On Mon, Aug 10, 2026 at 6:29 PM Tomasz Kaminski <[email protected]> wrote:
> > > On Mon, Aug 10, 2026 at 5:40 PM Tomasz Kamiński <[email protected]> > wrote: > >> From: Anlai Lu <[email protected]> >> >> Introduce _Streambuf_sink that writes directly to basic_streambuf >> via sputn, preferring zero-copy writes into the streambuf put area >> (pptr/epptr/pbump) and falling back to the stack buffer and bulk >> sputn. >> >> Streambuf I/O errors are tracked internally (_M_write_failed) and >> reported after formatting completes, so that format-phase exceptions >> (format_error, bad_alloc) propagate without setting badbit per >> [ostream.formatted.print]/(4.2). >> >> libstdc++-v3/ChangeLog: >> >> * include/std/format (__format::_Streambuf_sink): New class >> template. >> * include/std/streambuf: Include bits/version.h. >> (__format::_Streambuf_sink) [__glibcxx_format]: Declare. >> (std::basic_streambuf) [__glibcxx_format]: Befriend >> __format::_Streambuf_sink. >> >> Co-authored-by: Tomasz Kamiński <[email protected]> >> Signed-off-by: Anlai Lu <[email protected]> >> Signed-off-by: Tomasz Kamiński <[email protected]> >> --- >> Changes in v2: >> - call _M_reserve_put_area in _Streambuf_sink constructor, >> poiting buffer to put area >> - ostreambuf_iterator: Remove the changes, that were unrelated >> to standard >> - remove _GLIBCXX_CONSTEXPR_FORMAT, as they classes cannot >> be used at compile time >> - remove and inline _M_pbump and _M_use_stackbuf, rename >> _M_use_put_area to _M_reserve_put_area >> - done some formatting changes >> >> Testing on powerprc64. Tested on x86_64, additionally tested >> *format* and *print* in all standard modes, assertions and >> debug. OK for trunk? >> >> libstdc++-v3/include/std/format | 110 +++++++++++++++++++++++++++++ >> libstdc++-v3/include/std/streambuf | 9 +++ >> 2 files changed, 119 insertions(+) >> >> diff --git a/libstdc++-v3/include/std/format >> b/libstdc++-v3/include/std/format >> index 729cb89ec60..9223cd2e78b 100644 >> --- a/libstdc++-v3/include/std/format >> +++ b/libstdc++-v3/include/std/format >> @@ -3532,6 +3532,116 @@ namespace __format >> { } >> }; >> >> + // A format sink that writes directly to a basic_streambuf. >> + // Prefers zero-copy writes into the streambuf's put area >> + // (pptr/epptr/pbump), falling back to the stack buffer >> + // (_M_buf) and bulk sputn. >> + template<typename _CharT, typename _Traits = char_traits<_CharT>> >> + class _Streambuf_sink : public _Buf_sink<_CharT> >> + { >> + protected: >> + basic_streambuf<_CharT, _Traits>* _M_sbuf; >> + bool _M_write_failed = false; >> + >> + bool >> + _M_on_stack() const noexcept >> + { return this->_M_used().data() == this->_M_buf; } >> + >> + // Try to point our span directly into the streambuf's put >> + // area for zero-copy writes. Caller must fall back to >> + // _M_use_stackbuf() on failure. >> + bool >> + _M_reserve_put_area(size_t __n = 0) >> + { >> + if (auto __p = _M_sbuf->pptr()) >> + if (auto __e = _M_sbuf->epptr(); __e > __p) >> + if (size_t __a(__e - __p); __a >= __n) >> + { >> + this->_M_reset(span<_CharT>(__p, __a)); >> + return true; >> + } >> + return false; >> + } >> + >> + // Write characters to the streambuf, tracking errors. >> + // I/O exceptions from sputn are caught and converted to >> + // _M_write_failed so they do not propagate as vformat exceptions >> + // per [ostream.formatted.print]/(4.2). >> + void >> + _M_flush() >> + { >> + span<const _CharT> __s = this->_M_used(); >> + if (__s.empty() || _M_write_failed) [[unlikely]] >> + return; >> + >> + __try >> + { >> + if (!_M_on_stack()) >> + _M_sbuf->__safe_pbump(__s.size()); >> + else if (_M_sbuf->sputn(__s.data(), __s.size()) >> + != static_cast<streamsize>(__s.size())) >> + _M_write_failed = true; >> + } >> + __catch(const __cxxabiv1::__forced_unwind&) >> + { throw; } >> + __catch(...) >> + { _M_write_failed = true; } >> + } >> + >> + void >> + _M_overflow() override >> + { >> + _M_flush(); >> + if (!_M_reserve_put_area() || _M_write_failed) >> > We should check _M_write_failed first and make it unlikely. > Changed it locally to: _M_overflow() override { _M_flush(); - if (!_M_reserve_put_area() || _M_write_failed) - this->_M_reset(this->_M_buf); + if (!_M_write_failed) [[likely]] + if (_M_reserve_put_area()) + return; + this->_M_reset(this->_M_buf); } > + this->_M_reset(this->_M_buf); >> + } >> + >> + public: >> + [[__gnu__::__always_inline__]] >> + explicit >> + _Streambuf_sink(basic_streambuf<_CharT, _Traits>* __sbuf) noexcept >> + : _M_sbuf(__sbuf) >> + { _M_reserve_put_area(); } >> + >> + // _M_bump is not overridden: the default advances _M_next within >> + // the current span (stack buffer or put area). Actual commit >> + // to the streambuf happens via _M_flush, called from _M_overflow >> + // (buffer full) or _M_finish (end of formatting). >> + >> + typename _Sink<_CharT>::_Reservation >> + _M_reserve(size_t __n) override >> + { >> + if (__n <= this->_M_unused().size()) >> + return { this }; >> + >> + if (!this->_M_used().empty()) >> + _M_flush(); >> + >> + // Try to write directly into the streambuf's put area. >> + if (_M_reserve_put_area(__n)) >> + return { this }; >> + >> + // Otherwise reset to the stack buffer. >> + this->_M_reset(this->_M_buf); >> + if (__n <= this->_M_unused().size()) >> + return { this }; >> + >> + return { nullptr }; >> + } >> + >> + bool >> + _M_discarding() const override >> + { return _M_write_failed; } >> + >> + void >> + _M_finish() >> + { _M_flush(); } >> + >> + bool >> + _M_failed() const noexcept >> + { return _M_write_failed; } >> + }; >> + >> using _GLIBCXX_STD_C::vector; >> >> // A sink that fills a sequence (e.g. std::string, std::vector, >> std::deque). >> diff --git a/libstdc++-v3/include/std/streambuf >> b/libstdc++-v3/include/std/streambuf >> index 616e44f74a7..d273db2835f 100644 >> --- a/libstdc++-v3/include/std/streambuf >> +++ b/libstdc++-v3/include/std/streambuf >> @@ -39,6 +39,8 @@ >> >> #include <bits/requires_hosted.h> // iostreams >> >> +#include <bits/version.h> >> + >> #include <bits/c++config.h> >> #include <bits/iosfwd.h> >> #include <bits/localefwd.h> >> @@ -57,6 +59,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> __copy_streambufs_eof(basic_streambuf<_CharT, _Traits>*, >> basic_streambuf<_CharT, _Traits>*, bool&); >> >> +#ifdef __glibcxx_format // C++ >= 20 && HOSTED >> + namespace __format { template<typename, typename> class >> _Streambuf_sink; } >> +#endif >> + >> /** >> * @brief The actual work of input and output (interface). >> * @ingroup io >> @@ -149,6 +155,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> friend class basic_ostream<char_type, traits_type>; >> friend class istreambuf_iterator<char_type, traits_type>; >> friend class ostreambuf_iterator<char_type, traits_type>; >> +#ifdef __glibcxx_format // C++ >= 20 && HOSTED >> + friend class __format::_Streambuf_sink<char_type, traits_type>; >> +#endif >> >> friend streamsize >> __copy_streambufs_eof<>(basic_streambuf*, basic_streambuf*, bool&); >> -- >> 2.55.0 >> >>
