[PATCH v3 2/4] libstdc++: Add _Streambuf_sink for direct streambuf formatting
Tomasz Kamiński <[email protected]>
| Newsgroups | gmane.comp.gcc.patches,gmane.comp.gcc.libstdc++.devel |
|---|---|
| Message-ID | <[email protected]> |
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]> --- v3 makes the !_M_write_failed branch likely, as send previously. libstdc++-v3/include/std/format | 112 +++++++++++++++++++++++++++++ libstdc++-v3/include/std/streambuf | 9 +++ 2 files changed, 121 insertions(+) diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format index 729cb89ec60..f1698c3eb68 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -3532,6 +3532,118 @@ 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_write_failed) [[likely]] + if (_M_reserve_put_area()) + return; + 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