[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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.