https://gcc.gnu.org/g:8bd872f1ea74143aed0bd5148804a25dc6a9caf0

commit r16-4350-g8bd872f1ea74143aed0bd5148804a25dc6a9caf0
Author: Jonathan Wakely <[email protected]>
Date:   Wed Feb 21 16:11:53 2024 +0000

    libstdc++: Implement P3107R5 optimizations for std::print [PR121790]
    
    The names of the vprint functions follow the convention from P3235R3.
    
    This takes advantage of the additional permission proposed by P3107R5 so
    that std::print can write directly to a FILE stream, rather than
    formatting to an intermediate std::string temporary and then writing
    that to the stream. The change is to write to a new _File_sink type
    instead of a _Str_sink that populates a std::string. There are three
    implementations of _File_sink.
    
    For non-Glibc targets that support POSIX flockfile and putc_unlocked,
    the stream will be locked and then formatted characters will be buffered
    on the stack (instead of allocating a std::string) and copied to the
    stream when the buffer fills up.
    
    For Glibc, _File_sink will lock the stream but then if the file is
    line-buffered or fully buffered, characters will be written directly
    into the file's output buffer. This avoids two levels of buffering and
    copying the characters from one to the other. For an unbuffered stream
    (like stderr) the _File_sink buffer will still be used, to avoid the
    overhead of lots of small writes to the stream.  Because this version of
    _File_sink accesses the stream's buffer directly it relies on
    glibc-specific implementation details that are exposed in public
    headers.
    
    A fallback definition of _File_sink just wraps a _Str_sink so is
    equivalent to the original code, and is used when flockfile isn't
    available.
    
    Both forms of std::println (taking a FILE* and a std::ostream) can be
    implemented more efficiently by appending a newline to the format
    string, to avoid formatting twice.
    
            PR libstdc++/121790
    
    libstdc++-v3/ChangeLog:
    
            * acinclude.m4 (GLIBCXX_CHECK_STDIO_LOCKING): New macro to check
            for std::print dependencies.
            * config.h.in: Regenerate.
            * configure: Regenerate.
            * configure.ac: Use GLIBCXX_CHECK_STDIO_LOCKING.
            * include/bits/formatfwd.h 
(enable_nonlocking_formatter_optimization):
            Define new variable template.
            * include/bits/version.def (print): Bump value.
            * include/bits/version.h: Regenerate.
            * include/std/format (enable_nonlocking_formatter_optimization):
            Define specializations for variable template.
            * include/std/ostream (print) [!_WIN32]: Do not use
            vprint_unicode at all.
            (println): Append newline to format string instead of formatting
            twice.
            * include/std/print (_File_sink): New class.
            (vprint_nonunicode_locking): New function.
            (vprint_unicode_locking): New function reusing previous code
            from vprint_unicode.
            (vprintf_unicode): Defer to vprint_nonunicode for Windows or to
            vprint_unicode_locking otherwise.
            (print): [!_WIN32]: Do no use vprint_unicode at all.
            Check enable_nonlocking_formatter_optimization and defer to
            either vprint_nonunicode_locking or vprint_nonunicode.
            (println): Use vprint_unicode or format directly to a _File_sink
            instead of formatting twice.
            * testsuite/27_io/print/1.cc: Updated and added new tests.
            * testsuite/std/format/formatter/nonlocking.cc: New tests.
    
    Reviewed-by: Jonathan Wakely <[email protected]>
    Reviewed-by: Tomasz Kamiński <[email protected]>
    Co-authored-by: Tomasz Kamiński <[email protected]>

Diff:
---
 libstdc++-v3/acinclude.m4                          |  83 ++++++
 libstdc++-v3/config.h.in                           |   9 +
 libstdc++-v3/configure                             | 148 +++++++++++
 libstdc++-v3/configure.ac                          |   3 +
 libstdc++-v3/include/bits/formatfwd.h              |   5 +
 libstdc++-v3/include/bits/version.def              |   2 +-
 libstdc++-v3/include/bits/version.h                |   4 +-
 libstdc++-v3/include/std/format                    |  86 ++++++-
 libstdc++-v3/include/std/ostream                   |  17 +-
 libstdc++-v3/include/std/print                     | 284 ++++++++++++++++++++-
 libstdc++-v3/testsuite/27_io/print/1.cc            |  56 +++-
 .../testsuite/std/format/formatter/nonlocking.cc   |  59 +++++
 12 files changed, 733 insertions(+), 23 deletions(-)

diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4
index eb2d26286561..d040e8d30bee 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 
<stdio.h>])
+  AC_TRY_COMPILE([
+  #include <stdio.h>
+  ],[
+    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 <stdio.h>])
+    AC_TRY_COMPILE([
+    #include <stdio.h>
+    ],[
+      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 <stdio_ext.h> extensions.
+      case "${target_os}" in
+       gnu* | linux* | kfreebsd*-gnu | knetbsd*-gnu)
+         AC_MSG_CHECKING([for FILE::_IO_write_ptr and <stdio_ext.h>])
+         AC_TRY_COMPILE([
+         #include <stdio.h>
+         #include <stdio_ext.h>
+         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 818117aa6cce..4cfb9ba26be4 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 <unistd.h>. */
 #undef HAVE_GETENTROPY
 
@@ -828,6 +831,9 @@
 /* Define if get_nprocs is available in <sys/sysinfo.h>. */
 #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 <sys/sendfile.h>. */
 #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 713038b390bf..86ec969aaf15 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 <stdio.h>" >&5
+$as_echo_n "checking whether flockfile and putc_unlocked are defined in 
<stdio.h>... " >&6; }
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+  #include <stdio.h>
+
+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 <stdio.h>" >&5
+$as_echo_n "checking whether fwrite_unlocked is defined in <stdio.h>... " >&6; 
}
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+    #include <stdio.h>
+
+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 <stdio_ext.h> 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 <stdio_ext.h>" >&5
+$as_echo_n "checking for FILE::_IO_write_ptr and <stdio_ext.h>... " >&6; }
+         cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+         #include <stdio.h>
+         #include <stdio_ext.h>
+         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 0bf219174fe7..47813eb95ca6 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 314b55d50bcd..883b772752a7 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<typename>
+    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 7c91a18c6861..1c0f43e465b5 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 7ba78774041a..7b97accc47e1 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 842972eed4ca..1d01bc39e9c3 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<const _CharT*, _CharT>
     {
@@ -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<const _CharT*> = 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<typename _Traits, typename _Alloc>
     struct formatter<basic_string<char, _Traits, _Alloc>, char>
     {
@@ -2733,6 +2754,13 @@ namespace __format
       __format::__formatter_str<char> _M_f;
     };
 
+#if __glibcxx_print >= 202403L
+  template<typename _Tr, typename _Alloc>
+    constexpr bool
+    enable_nonlocking_formatter_optimization<basic_string<char, _Tr, _Alloc>>
+      = true;
+#endif
+
 #ifdef _GLIBCXX_USE_WCHAR_T
   template<typename _Traits, typename _Alloc>
     struct formatter<basic_string<wchar_t, _Traits, _Alloc>, wchar_t>
@@ -2757,6 +2785,14 @@ namespace __format
     private:
       __format::__formatter_str<wchar_t> _M_f;
     };
+
+#if __glibcxx_print >= 202403L
+  template<typename _Tr, typename _Alloc>
+    constexpr bool
+    enable_nonlocking_formatter_optimization<basic_string<wchar_t, _Tr, 
_Alloc>>
+      = true;
+#endif
+
 #endif // USE_WCHAR_T
 
   template<typename _Traits>
@@ -2783,6 +2819,13 @@ namespace __format
       __format::__formatter_str<char> _M_f;
     };
 
+#if __glibcxx_print >= 202403L
+  template<typename _Tr>
+    constexpr bool
+    enable_nonlocking_formatter_optimization<basic_string_view<char, _Tr>>
+      = true;
+#endif
+
 #ifdef _GLIBCXX_USE_WCHAR_T
   template<typename _Traits>
     struct formatter<basic_string_view<wchar_t, _Traits>, wchar_t>
@@ -2807,6 +2850,13 @@ namespace __format
     private:
       __format::__formatter_str<wchar_t> _M_f;
     };
+
+#if __glibcxx_print >= 202403L
+  template<typename _Tr>
+    constexpr bool
+    enable_nonlocking_formatter_optimization<basic_string_view<wchar_t, _Tr>>
+      = true;
+#endif
 #endif // USE_WCHAR_T
   /// @}
 
@@ -2831,12 +2881,14 @@ namespace __format
 #endif
   template<> inline constexpr bool __is_formattable_integer<char16_t> = false;
   template<> inline constexpr bool __is_formattable_integer<char32_t> = false;
+
+  template<typename _Tp>
+    concept __formattable_integer = __is_formattable_integer<_Tp>;
 }
 /// @endcond
 
   /// Format an integer.
-  template<typename _Tp, __format::__char _CharT>
-    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<C>::format<double, Out> 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<const void*> = true;
+#endif
+
   template<__format::__char _CharT>
     struct formatter<void*, _CharT>
     {
@@ -3075,6 +3145,12 @@ namespace __format
       __format::__formatter_ptr<_CharT> _M_f;
     };
 
+#if __glibcxx_print >= 202403l
+  template<>
+    inline constexpr bool
+    enable_nonlocking_formatter_optimization<void*> = true;
+#endif
+
   template<__format::__char _CharT>
     struct formatter<nullptr_t, _CharT>
     {
@@ -3095,6 +3171,12 @@ namespace __format
     };
   /// @}
 
+#if __glibcxx_print >= 202403L
+  template<>
+    inline constexpr bool
+    enable_nonlocking_formatter_optimization<nullptr_t> = 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 3a0a0d35df1d..33872969fe06 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 92dbe118fc31..6ffd9a4b1b3f 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 <stdio_ext.h> 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<char>
+  {
+    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<char>
+      _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<char>::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<char>
+  {
+    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<char>(), _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<char>::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<char> _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<char> __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<typename... _Args>
     inline void
     print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args)
     {
+      constexpr bool __locksafe =
+       (enable_nonlocking_formatter_optimization<remove_cvref_t<_Args>> && 
...);
+
       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<typename... _Args>
@@ -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<remove_cvref_t<_Args>> && 
...);
+
+      // 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<char> __buf;
+         std::vformat_to(__buf.out(), __fmt.get(), __fmtargs);
+         string_view __s(__buf.view());
+         __format::_File_sink(__stream, true).out() = __s;
+       }
     }
 
   template<typename... _Args>
@@ -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 2a74e5002f44..58f1eb163dfa 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<typename CharT>
+struct std::formatter<ThrowOnFormat, CharT>
+{
+  constexpr typename std::basic_format_parse_context<CharT>::iterator
+  parse(const std::basic_format_parse_context<CharT>& pc) const
+  { return pc.begin(); }
+
+  template<typename Out>
+  typename std::basic_format_context<Out, CharT>::iterator
+  format(ThrowOnFormat, const std::basic_format_context<Out, CharT>&) 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<char>(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 000000000000..a726e9d74cee
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/formatter/nonlocking.cc
@@ -0,0 +1,59 @@
+// { dg-do compile { target c++23 } }
+
+#include <format>
+#include <string>
+
+template<typename CharT>
+struct MyTraits : std::char_traits<CharT>
+{};
+
+template<typename CharT>
+struct MyAlloc : std::allocator<CharT>
+{
+  using std::allocator<CharT>::allocator;
+};
+
+template<typename CharT>
+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<CharT>>);
+  static_assert(std::enable_nonlocking_formatter_optimization<
+                 std::basic_string<CharT, MyTraits<CharT>>>);
+  static_assert(std::enable_nonlocking_formatter_optimization<
+                 std::basic_string<CharT, MyTraits<CharT>, MyAlloc<CharT>>>);
+
+  static_assert(std::enable_nonlocking_formatter_optimization<
+                 std::basic_string_view<CharT>>);
+  static_assert(std::enable_nonlocking_formatter_optimization<
+                 std::basic_string_view<CharT, MyTraits<CharT>>>);
+}
+
+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<char>();
+#ifdef _GLIBCXX_USE_WCHAR_T
+  testCharacters<wchar_t>();
+#endif // USE_WCHAR_T
+}
+

Reply via email to