[1/2] libstdc++: Implement C++23 <print> header [PR107760]

Message ID 20231117160320.1513815-1-jwakely@redhat.com
State Superseded
Headers
Series [1/2] libstdc++: Implement C++23 <print> header [PR107760] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm fail Patch failed to apply
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 fail Patch failed to apply

Commit Message

Jonathan Wakely Nov. 17, 2023, 3:54 p.m. UTC
  There's a TODO here about checking for invalid UTF-8, which is done by
the next patch.

I don't know if the Windows code actually works. I tried to test it with
mingw and Wine, but I got garbled text. But I'm not sure if that's my
code here, or the conversion to UTF-16, or how I'm testing, or just that
Wine in a Linux terminal doesn't properly emulat the Windows console, or
something else.

This needs tests, so I need to write them before pushing, but I still
plan to get that done for GCC 14.

-- >8 --

libstdc++-v3/ChangeLog:

	PR libstdc++/107760
	* config/abi/pre/gnu.ver: Export new symbols.
	* include/Makefile.am: Add new header.
	* include/Makefile.in: Regenerate.
	* include/bits/version.def (__cpp_lib_print): Define.
	* include/bits/version.h: Regenerate.
	* include/std/ostream (vprintf_nonunicode, vprintf_unicode)
	(print, println): New functions.
	* include/std/print: New file.
	* src/c++20/Makefile.am: Add new source file.
	* src/c++20/Makefile.in: Regenerate.
	* src/c++98/globals_io.cc [_WIN32] (__fd_for_console): New
	function.
	* src/c++20/print.cc: New file.
---
 libstdc++-v3/config/abi/pre/gnu.ver   |   4 +
 libstdc++-v3/include/Makefile.am      |   1 +
 libstdc++-v3/include/Makefile.in      |   1 +
 libstdc++-v3/include/bits/version.def |   9 ++
 libstdc++-v3/include/bits/version.h   |  29 ++++--
 libstdc++-v3/include/std/ostream      | 102 ++++++++++++++++++++
 libstdc++-v3/include/std/print        | 128 ++++++++++++++++++++++++++
 libstdc++-v3/src/c++20/Makefile.am    |   2 +-
 libstdc++-v3/src/c++20/Makefile.in    |   4 +-
 libstdc++-v3/src/c++20/print.cc       |  35 +++++++
 libstdc++-v3/src/c++98/globals_io.cc  |  23 +++++
 11 files changed, 326 insertions(+), 12 deletions(-)
 create mode 100644 libstdc++-v3/include/std/print
 create mode 100644 libstdc++-v3/src/c++20/print.cc
  

Patch

diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver
index 15b50d51251..c7200929e34 100644
--- a/libstdc++-v3/config/abi/pre/gnu.ver
+++ b/libstdc++-v3/config/abi/pre/gnu.ver
@@ -2514,6 +2514,10 @@  GLIBCXX_3.4.31 {
     _ZNKSt12__shared_ptrINSt10filesystem28recursive_directory_iterator10_Dir_stackELN9__gnu_cxx12_Lock_policyE[012]EEcvbEv;
     _ZNKSt12__shared_ptrINSt10filesystem7__cxx1128recursive_directory_iterator10_Dir_stackELN9__gnu_cxx12_Lock_policyE[012]EEcvbEv;
 
+    # These are only defined for *-*-mingw*
+    _ZSt16__fd_for_consolePSt15basic_streambufIcSt11char_traitsIcEE;
+    _ZSt24__write_utf16_to_consoleiNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE;
+
 } GLIBCXX_3.4.30;
 
 GLIBCXX_3.4.32 {
diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am
index 17d9d9cec31..368b92eafbc 100644
--- a/libstdc++-v3/include/Makefile.am
+++ b/libstdc++-v3/include/Makefile.am
@@ -85,6 +85,7 @@  std_headers = \
 	${std_srcdir}/memory_resource \
 	${std_srcdir}/mutex \
 	${std_srcdir}/ostream \
+	${std_srcdir}/print \
 	${std_srcdir}/queue \
 	${std_srcdir}/random \
 	${std_srcdir}/regex \
diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in
index f038af709cc..a31588c0100 100644
--- a/libstdc++-v3/include/Makefile.in
+++ b/libstdc++-v3/include/Makefile.in
@@ -441,6 +441,7 @@  std_freestanding = \
 @GLIBCXX_HOSTED_TRUE@	${std_srcdir}/memory_resource \
 @GLIBCXX_HOSTED_TRUE@	${std_srcdir}/mutex \
 @GLIBCXX_HOSTED_TRUE@	${std_srcdir}/ostream \
+@GLIBCXX_HOSTED_TRUE@	${std_srcdir}/print \
 @GLIBCXX_HOSTED_TRUE@	${std_srcdir}/queue \
 @GLIBCXX_HOSTED_TRUE@	${std_srcdir}/random \
 @GLIBCXX_HOSTED_TRUE@	${std_srcdir}/regex \
diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 15bd502f52c..8b5cace3775 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1578,6 +1578,15 @@  ftms = {
   };
 };
 
+ftms = {
+  name = print;
+  values = {
+    v = 202211;
+    cxxmin = 23;
+    hosted = yes;
+  };
+};
+
 ftms = {
   name = spanstream;
   values = {
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 9563b6cd2f7..f197408e60f 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -1923,6 +1923,17 @@ 
 #undef __glibcxx_want_out_ptr
 
 // from version.def line 1582
+#if !defined(__cpp_lib_print)
+# if (__cplusplus >= 202100L) && _GLIBCXX_HOSTED
+#  define __glibcxx_print 202211L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_print)
+#   define __cpp_lib_print 202211L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_print) && defined(__glibcxx_want_print) */
+#undef __glibcxx_want_print
+
+// from version.def line 1591
 #if !defined(__cpp_lib_spanstream)
 # if (__cplusplus >= 202100L) && _GLIBCXX_HOSTED && (__glibcxx_span)
 #  define __glibcxx_spanstream 202106L
diff --git a/libstdc++-v3/include/std/ostream b/libstdc++-v3/include/std/ostream
index 1de1c1bd359..e81c39a7c80 100644
--- a/libstdc++-v3/include/std/ostream
+++ b/libstdc++-v3/include/std/ostream
@@ -39,6 +39,11 @@ 
 
 #include <ios>
 #include <bits/ostream_insert.h>
+#if __cplusplus > 202002L
+# include <format>
+#endif
+
+# define __glibcxx_want_print
 #include <bits/version.h> // __glibcxx_syncbuf
 
 namespace std _GLIBCXX_VISIBILITY(default)
@@ -872,6 +877,103 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
 #endif // __glibcxx_syncbuf
 
+#if __cpp_lib_print // C++ >= 23
+
+  inline void
+  vprint_nonunicode(ostream& __os, string_view __fmt, format_args __args)
+  {
+    ostream::sentry __cerb(__os);
+    if (__cerb)
+      {
+	string __out = std::vformat(__fmt, __args);
+	__try
+	  {
+	    const streamsize __w = __os.width();
+	    const streamsize __n = __out.size();
+	    if (__w > __n)
+	      {
+		const bool __left
+		  = (__os.flags() & ios_base::adjustfield) == ios_base::left;
+		if (!__left)
+		  std::__ostream_fill(__os, __w - __n);
+		if (__os.good())
+		  std::__ostream_write(__os, __out.data(), __n);
+		if (__left && __os.good())
+		  std::__ostream_fill(__os, __w - __n);
+	      }
+	    else
+	      std::__ostream_write(__os, __out.data(), __n);
+	  }
+	__catch(const __cxxabiv1::__forced_unwind&)
+	  {
+	    __os._M_setstate(ios_base::badbit);
+	    __throw_exception_again;
+	  }
+	__catch(...)
+	  { __os._M_setstate(ios_base::badbit); }
+      }
+  }
+
+  inline void
+  vprint_unicode(ostream& __os, string_view __fmt, format_args __args)
+  {
+    // TODO: diagnose invalid UTF-8 code units
+#ifdef _WIN32
+    int __fd_for_console(std::streambuf*);
+    void __write_utf16_to_console(int, string);
+
+    // If stream refers to a terminal convert to UTF-16 and use WriteConsoleW.
+    if (int __fd = __fd_for_console(__os.rdbuf()); __fd >= 0)
+      {
+	ostream::sentry __cerb(__os);
+	if (__cerb)
+	  {
+	    string __out = std::vformat(__fmt, __args);
+	    ios_base::iostate __err = ios_base::goodbit;
+	    __try
+	      {
+		if (__os.rdbuf()->pubsync() == -1)
+		  __err = ios::badbit;
+		else if (__write_utf16_to_console(__fd, __out))
+		  __err = ios::badbit;
+	      }
+	    __catch(const __cxxabiv1::__forced_unwind&)
+	      {
+		__os._M_setstate(ios_base::badbit);
+		__throw_exception_again;
+	      }
+	    __catch(...)
+	      { __os._M_setstate(ios_base::badbit); }
+
+	    if (__err)
+	      __os.setstate(__err);
+	  }
+      }
+#endif
+    std::vprint_nonunicode(__os, __fmt, __args);
+  }
+
+
+  template<typename... _Args>
+    inline void
+    print(ostream& __os, format_string<_Args...> __fmt, _Args&&... __args)
+    {
+      auto __fmtargs = std::make_format_args(std::forward<_Args>(__args)...);
+      if constexpr (string_view(__GNUC_EXECUTION_CHARSET_NAME) == "UTF-8")
+	std::vprint_unicode(__os, __fmt.get(), __fmtargs);
+      else
+	std::vprint_nonunicode(__os, __fmt.get(), __fmtargs);
+    }
+
+  template<typename... _Args>
+    inline void
+    println(ostream& __os, format_string<_Args...> __fmt, _Args&&... __args)
+    {
+      std::print(__os, "{}\n",
+		 std::format(__fmt, std::forward<_Args>(__args)...));
+    }
+#endif // __cpp_lib_print
+
 #endif // C++11
 
 _GLIBCXX_END_NAMESPACE_VERSION
diff --git a/libstdc++-v3/include/std/print b/libstdc++-v3/include/std/print
new file mode 100644
index 00000000000..75e78841247
--- /dev/null
+++ b/libstdc++-v3/include/std/print
@@ -0,0 +1,128 @@ 
+// <print> Print functions -*- C++ -*-
+
+// Copyright The GNU Toolchain Authors.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+/** @file include/print
+ *  This is a Standard C++ Library header.
+ */
+
+#ifndef _GLIBCXX_PRINT
+#define _GLIBCXX_PRINT 1
+
+#pragma GCC system_header
+
+#include <bits/requires_hosted.h> // for std::format
+
+#define __glibcxx_want_print
+#include <bits/version.h>
+
+#ifdef __cpp_lib_print // C++ >= 23
+
+#include <format>
+#include <cstdio>
+#include <cerrno>
+#include <bits/functexcept.h>
+
+#ifdef _WIN32
+# include <system_error>
+#endif
+
+namespace std _GLIBCXX_VISIBILITY(default)
+{
+_GLIBCXX_BEGIN_NAMESPACE_VERSION
+
+  inline void
+  vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args)
+  {
+    string __out = std::vformat(__fmt, __args);
+    if (std::fwrite(__out.data(), 1, __out.size(), __stream) != __out.size())
+      __throw_system_error(EIO);
+  }
+
+  inline void
+  vprint_unicode(FILE* __stream, string_view __fmt, format_args __args)
+  {
+    // TODO: diagnose invalid UTF-8 code units
+#ifdef _WIN32
+    int __fd_for_console(FILE*);
+    void __write_utf16_to_console(int, string);
+
+    // If stream refers to a terminal convert to UTF-16 and use WriteConsoleW.
+    if (int __fd = __fd_for_console(__stream); __fd >= 0)
+      {
+	string __out = std::vformat(__fmt, __args);
+	error_code __e;
+	if (!std::fflush(__stream))
+	  {
+	    if (!(__e = __write_utf16_to_console(__fd, __out)))
+	      return;
+	  }
+	else
+	  __e = error_code(errno, generic_category());
+	_GLIBCXX_THROW_OR_ABORT(system_error(__e, "std::vprint_unicode"));
+      }
+#endif
+    std::vprint_nonunicode(__stream, __fmt, __args);
+  }
+
+  template<typename... _Args>
+    inline void
+    print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args)
+    {
+      auto __fmtargs = std::make_format_args(std::forward<_Args>(__args)...);
+      if constexpr (string_view(__GNUC_EXECUTION_CHARSET_NAME) == "UTF-8")
+	std::vprint_unicode(__stream, __fmt.get(), __fmtargs);
+      else
+	std::vprint_nonunicode(__stream, __fmt.get(), __fmtargs);
+    }
+
+  template<typename... _Args>
+    inline void
+    print(format_string<_Args...> __fmt, _Args&&... __args)
+    { std::print(stdout, __fmt, std::forward<_Args>(__args)...); }
+
+  template<typename... _Args>
+    inline void
+    println(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args)
+    {
+      std::print(__stream, "{}\n",
+		 std::format(__fmt, std::forward<_Args>(__args)...));
+    }
+
+  template<typename... _Args>
+    inline void
+    println(format_string<_Args...> __fmt, _Args&&... __args)
+    { std::println(stdout, __fmt, std::forward<_Args>(__args)...); }
+
+  inline void
+  vprint_unicode(string_view __fmt, format_args __args)
+  { std::vprint_unicode(stdout, __fmt, __args); }
+
+  inline void
+  vprint_nonunicode(string_view __fmt, format_args __args)
+  { std::vprint_nonunicode(stdout, __fmt, __args); }
+
+_GLIBCXX_END_NAMESPACE_VERSION
+} // namespace std
+#endif // __cpp_lib_print
+#endif // _GLIBCXX_PRINT
diff --git a/libstdc++-v3/src/c++20/Makefile.am b/libstdc++-v3/src/c++20/Makefile.am
index e947855e6ae..3cdc6521bb4 100644
--- a/libstdc++-v3/src/c++20/Makefile.am
+++ b/libstdc++-v3/src/c++20/Makefile.am
@@ -36,7 +36,7 @@  else
 inst_sources =
 endif
 
-sources = tzdb.cc
+sources = tzdb.cc print.cc
 
 vpath % $(top_srcdir)/src/c++20
 
diff --git a/libstdc++-v3/src/c++20/Makefile.in b/libstdc++-v3/src/c++20/Makefile.in
index 3ec8c5ce804..b732e6fc005 100644
--- a/libstdc++-v3/src/c++20/Makefile.in
+++ b/libstdc++-v3/src/c++20/Makefile.in
@@ -121,7 +121,7 @@  CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 LTLIBRARIES = $(noinst_LTLIBRARIES)
 libc__20convenience_la_LIBADD =
-am__objects_1 = tzdb.lo
+am__objects_1 = tzdb.lo print.lo
 @ENABLE_EXTERN_TEMPLATE_TRUE@am__objects_2 = sstream-inst.lo
 @GLIBCXX_HOSTED_TRUE@am_libc__20convenience_la_OBJECTS =  \
 @GLIBCXX_HOSTED_TRUE@	$(am__objects_1) $(am__objects_2)
@@ -432,7 +432,7 @@  headers =
 @ENABLE_EXTERN_TEMPLATE_TRUE@inst_sources = \
 @ENABLE_EXTERN_TEMPLATE_TRUE@	sstream-inst.cc
 
-sources = tzdb.cc
+sources = tzdb.cc print.cc
 @GLIBCXX_HOSTED_FALSE@libc__20convenience_la_SOURCES = 
 @GLIBCXX_HOSTED_TRUE@libc__20convenience_la_SOURCES = $(sources)  $(inst_sources)
 
diff --git a/libstdc++-v3/src/c++20/print.cc b/libstdc++-v3/src/c++20/print.cc
new file mode 100644
index 00000000000..d97a0c71dfe
--- /dev/null
+++ b/libstdc++-v3/src/c++20/print.cc
@@ -0,0 +1,35 @@ 
+#ifdef _WIN32
+#include <system_error>
+#include <codecvt>
+#include <bits/locale_conv.h>
+#include <stdio.h>   // _fileno
+#include <io.h>      // _get_osfhandle, _isatty
+#include <windows.h> // GetLastError, WriteConsoleW
+
+namespace std _GLIBCXX_VISIBILITY(default)
+{
+  int
+  __fd_for_console(FILE* f)
+  {
+    if (int fd = _fileno(f); fd >= 0 && _isatty(fd))
+      return fd;
+    return -1;
+  }
+
+  error_code
+  __write_utf16_to_console(int fd, string str)
+  {
+    std::wstring wstr;
+    std::codecvt_utf8_utf16<wchar_t> wcvt;
+    const auto p = str.data();
+    unsigned long nchars = 0;
+    if (!std::__str_codecvt_in_all(p, p + str.size(), wstr, wcvt))
+      return std::make_error_code(errc::illegal_byte_sequence);
+    WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)),
+	wstr.data(), wstr.size(), &nchars, nullptr);
+    if (nchars != wstr.size())
+      return {(int)GetLastError(), system_category()};
+    return {};
+  }
+}
+#endif // _WIN32
diff --git a/libstdc++-v3/src/c++98/globals_io.cc b/libstdc++-v3/src/c++98/globals_io.cc
index 0c4f270977d..538efd01b53 100644
--- a/libstdc++-v3/src/c++98/globals_io.cc
+++ b/libstdc++-v3/src/c++98/globals_io.cc
@@ -107,3 +107,26 @@  namespace __gnu_internal _GLIBCXX_VISIBILITY(hidden)
   fake_wfilebuf buf_wcerr;
 #endif
 } // namespace __gnu_internal
+
+#ifdef _WIN32
+namespace std _GLIBCXX_VISIBILITY(default)
+{
+  int __fd_for_console(FILE*);
+
+  int __fd_for_console(std::streambuf* sb)
+  {
+    using namespace __gnu_internal;
+    using namespace __gnu_cxx;
+
+    FILE* f = NULL;
+    void* p = sb;
+    if (p == buf_cout_sync || p == buf_cin_sync || p == buf_cerr_sync)
+      f = dynamic_cast<stdio_sync_filebuf<char>*>(sb)->file();
+    else if (p == buf_cout || p == buf_cin || p == buf_cerr)
+      f = dynamic_cast<stdio_filebuf<char>*>(sb)->file();
+    else
+      return -1;
+    return __fd_for_console(f);
+  }
+}
+#endif