mirror of
https://github.com/gcc-mirror/gcc.git
synced 2026-01-19 00:06:47 +08:00
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 <jwakely@redhat.com> Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com> Co-authored-by: Tomasz Kamiński <tkaminsk@redhat.com>
423 lines
12 KiB
C++
423 lines
12 KiB
C++
// <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
|
|
|
|
#ifdef _GLIBCXX_SYSHDR
|
|
#pragma GCC system_header
|
|
#endif
|
|
|
|
#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
|
|
|
|
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);
|
|
auto __out = __buf.view();
|
|
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)
|
|
{
|
|
#if !defined(_WIN32) || defined(__CYGWIN__)
|
|
// For most targets we don't need to do anything special to write
|
|
// Unicode to a terminal.
|
|
std::vprint_nonunicode(__stream, __fmt, __args);
|
|
#else
|
|
__format::_Str_sink<char> __buf;
|
|
std::vformat_to(__buf.out(), __fmt, __args);
|
|
auto __out = __buf._M_span();
|
|
|
|
void* __open_terminal(FILE*);
|
|
error_code __write_to_terminal(void*, span<char>);
|
|
// If stream refers to a terminal, write a native Unicode string to it.
|
|
if (auto __term = __open_terminal(__stream))
|
|
{
|
|
error_code __e;
|
|
if (!std::fflush(__stream))
|
|
{
|
|
__e = __write_to_terminal(__term, __out);
|
|
if (!__e)
|
|
return;
|
|
if (__e == std::make_error_code(errc::illegal_byte_sequence))
|
|
return;
|
|
}
|
|
else
|
|
__e = error_code(errno, generic_category());
|
|
_GLIBCXX_THROW_OR_ABORT(system_error(__e, "std::vprint_unicode"));
|
|
}
|
|
|
|
// 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_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>
|
|
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)
|
|
{
|
|
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>
|
|
inline void
|
|
println(format_string<_Args...> __fmt, _Args&&... __args)
|
|
{ std::println(stdout, __fmt, std::forward<_Args>(__args)...); }
|
|
|
|
inline void
|
|
vprint_unicode_buffered(string_view __fmt, format_args __args)
|
|
{ std::vprint_unicode_buffered(stdout, __fmt, __args); }
|
|
|
|
inline void
|
|
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_buffered(__stream, "\n", std::make_format_args());
|
|
else
|
|
#endif
|
|
if (std::putc('\n', __stream) == EOF)
|
|
__throw_system_error(EIO);
|
|
}
|
|
|
|
inline void println() { std::println(stdout); }
|
|
|
|
_GLIBCXX_END_NAMESPACE_VERSION
|
|
} // namespace std
|
|
#endif // __cpp_lib_print
|
|
#endif // _GLIBCXX_PRINT
|