Re: [PATCH 2/4] libstdc++: Add _Streambuf_sink for direct streambuf formatting
Tomasz Kaminski <[email protected]>
| Newsgroups | gmane.comp.gcc.patches,gmane.comp.gcc.libstdc++.devel |
|---|---|
| Message-ID | <CAKvuMXAX6OSn75YLgAwDwx4vaijeqc0sZLLT+zD=Ku60RJEPpw@mail.gmail.com> |
On Thu, Jul 16, 2026 at 2:04 PM Anlai Lu <[email protected]> wrote: > 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). > > The _Sink_state enum is omitted in favor of _M_on_stack() which > tests whether the current span points at the stack buffer. > > libstdc++-v3/ChangeLog: > > * include/bits/streambuf_iterator.h (ostreambuf_iterator): > Add internal _M_get_sbuf() and _M_set_failed() members. > * include/std/format: Include <bits/streambuf_iterator.h>. > (__format::_Streambuf_sink): New class template. > * include/std/streambuf: Forward-declare __format::_Streambuf_sink, > add friend declaration to basic_streambuf. > > Signed-off-by: Anlai Lu <[email protected]> > --- > .../include/bits/streambuf_iterator.h | 11 ++ > libstdc++-v3/include/std/format | 132 ++++++++++++++++++ > libstdc++-v3/include/std/streambuf | 4 + > 3 files changed, 147 insertions(+) > I will post updated patch with changes made, but makign some commit. > > diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h > b/libstdc++-v3/include/bits/streambuf_iterator.h > index 095928ca4..d919fdbb8 100644 > --- a/libstdc++-v3/include/bits/streambuf_iterator.h > +++ b/libstdc++-v3/include/bits/streambuf_iterator.h > @@ -318,6 +318,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > failed() const _GLIBCXX_USE_NOEXCEPT > { return _M_failed; } > > + /// @cond internal > + _GLIBCXX_NODISCARD > + streambuf_type* > + _M_get_sbuf() const _GLIBCXX_USE_NOEXCEPT > + { return _M_sbuf; } > + > + void > + _M_set_failed() _GLIBCXX_USE_NOEXCEPT > + { _M_failed = true; } > + /// @endcond > + > This should go to next patch, and we should befriend the _Iter_sink specialization instead. > ostreambuf_iterator& > _M_put(const _CharT* __ws, streamsize __len) > { > diff --git a/libstdc++-v3/include/std/format > b/libstdc++-v3/include/std/format > index 9bf5e7857..b676629b1 100644 > --- a/libstdc++-v3/include/std/format > +++ b/libstdc++-v3/include/std/format > @@ -59,6 +59,7 @@ > #include <bits/ranges_util.h> // subrange > #include <bits/ranges_algobase.h> // ranges::copy > #include <bits/stl_iterator.h> // counted_iterator > +#include <bits/streambuf_iterator.h> // ostreambuf_iterator, > basic_streambuf > #include <bits/stl_pair.h> // __is_pair > #include <bits/unicode.h> // __is_scalar_value, _Utf_view, etc. > #include <bits/utility.h> // tuple_size_v > @@ -3530,6 +3531,137 @@ 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> > + { > + using _Buf_sink<_CharT>::_M_buf; > + > + protected: > + basic_streambuf<_CharT, _Traits>* _M_sbuf; > + bool _M_write_failed = false; > + > + _GLIBCXX_CONSTEXPR_FORMAT bool > + _M_on_stack() const noexcept > + { return this->_M_used().data() == this->_M_buf; } > + > + // Switch to _M_buf when the streambuf has no put area > + // or we need to stop using it. > + _GLIBCXX_CONSTEXPR_FORMAT void > + _M_use_stackbuf() > + { this->_M_reset(_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. > + _GLIBCXX_CONSTEXPR_FORMAT bool > + _M_use_put_area(size_t __n = 0) > + { > + if (auto __p = _M_sbuf->pptr()) > + { > + auto __e = _M_sbuf->epptr(); > + if (__e && __e > __p > + && static_cast<size_t>(__e - __p) >= __n) > We could use some if with initializer here. > + { > + this->_M_reset( > + span<_CharT>{__p, static_cast<size_t>(__e - __p)}); > + return true; > + } > + } > + return false; > + } > + > + // Allow derived classes to commit to the put area > + // without needing friendship to basic_streambuf. > + _GLIBCXX_CONSTEXPR_FORMAT void > + _M_pbump(streamsize __n) > + { _M_sbuf->__safe_pbump(__n); } > + > + // 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). Does not refresh the > + // span; callers use _M_use_put_area/stackbuf. > + _GLIBCXX_CONSTEXPR_FORMAT void > + _M_flush(span<_CharT> __s) > + { > + if (__s.empty() || _M_write_failed) [[unlikely]] > + return; > + __try { > + if (_M_on_stack()) > + { > + if (_M_sbuf->sputn(__s.data(), __s.size()) > + != static_cast<streamsize>(__s.size())) > + _M_write_failed = true; > + } > + else > + _M_pbump(__s.size()); > + } > + __catch(const __cxxabiv1::__forced_unwind&) > + { throw; } > + __catch(...) > + { _M_write_failed = true; } > + } > + > + _GLIBCXX_CONSTEXPR_FORMAT void > This should not be cosntexpr, as they cannot be never used at compile time. > + _M_overflow() override > + { > + _M_flush(this->_M_used()); > + if (!_M_use_put_area()) > + _M_use_stackbuf(); > + } > + > + public: > + [[__gnu__::__always_inline__]] > + _GLIBCXX_CONSTEXPR_FORMAT explicit > + _Streambuf_sink(basic_streambuf<_CharT, _Traits>* __sbuf) noexcept > + : _M_sbuf(__sbuf) > + { } > This is important part, we are currently allocating the buffer on stack here, and we will copy it at least once (this hid some bugs in _Iter_sink, when we allowed it to write more than n characters). We should instead call _M_use_put_area() in constructor, so we achieve true zero-copy here, by writting and reserving directly from it. > + > + using _Sink<_CharT>::out; > + > + // _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). > + > + _GLIBCXX_CONSTEXPR_FORMAT typename _Sink<_CharT>::_Reservation > + _M_reserve(size_t __n) override > + { > + if (__n <= this->_M_unused().size()) > + return { this }; > + > + if (!this->_M_used().empty()) > + _M_overflow(); > + > + // Try to write directly into the streambuf's put area. > + if (_M_use_put_area(__n)) > + return { this }; > + > + // Otherwise reset to the stack buffer. > + _M_use_stackbuf(); > + if (__n <= this->_M_unused().size()) > + return { this }; > + > + return { nullptr }; > + } > + > + _GLIBCXX_CONSTEXPR_FORMAT bool > + _M_discarding() const override > + { return _M_write_failed; } > + > + _GLIBCXX_CONSTEXPR_FORMAT void > + _M_finish() && > + { _M_flush(this->_M_used()); } > + > + _GLIBCXX_CONSTEXPR_FORMAT 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 616e44f74..22b505228 100644 > --- a/libstdc++-v3/include/std/streambuf > +++ b/libstdc++-v3/include/std/streambuf > @@ -57,6 +57,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > __copy_streambufs_eof(basic_streambuf<_CharT, _Traits>*, > basic_streambuf<_CharT, _Traits>*, bool&); > > + namespace __format { template<typename, typename> class > _Streambuf_sink; } > + > /** > * @brief The actual work of input and output (interface). > * @ingroup io > @@ -149,6 +151,8 @@ _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>; > + template<typename, typename> > + friend class __format::_Streambuf_sink; > > friend streamsize > __copy_streambufs_eof<>(basic_streambuf*, basic_streambuf*, bool&); > -- > 2.34.1 > >