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.

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               | 163 ++++++++++++++++++
 libstdc++-v3/include/std/streambuf            |   4 +
 3 files changed, 178 insertions(+)

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
+
       ostreambuf_iterator&
       _M_put(const _CharT* __ws, streamsize __len)
       {
diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index dd275b6b8..a449efd45 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -60,6 +60,7 @@
 #include <bits/ranges_algobase.h> // ranges::copy
 #include <bits/stl_iterator.h> // counted_iterator
 #include <bits/stl_pair.h>     // __is_pair
+#include <bits/streambuf_iterator.h> // ostreambuf_iterator, basic_streambuf
 #include <bits/unicode.h>      // __is_scalar_value, _Utf_view, etc.
 #include <bits/utility.h>      // tuple_size_v
 #include <ext/numeric_traits.h> // __int_traits
@@ -3531,6 +3532,168 @@ 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.
+  // _M_count tracks chars output (for _M_max truncation decisions);
+  // _M_total tracks all chars formatted (for format_to_n_result::size).
+  template<typename _CharT, typename _Traits = char_traits<_CharT>>
+    class _Streambuf_sink : public _Buf_sink<_CharT>
+    {
+      using _Buf_sink<_CharT>::_M_buf;
+      using __diff_t
+       = iter_difference_t<ostreambuf_iterator<_CharT, _Traits>>;
+      enum class _Sink_state { _S_stack, _S_put_area, _S_failed };
+
+      basic_streambuf<_CharT, _Traits>* _M_sbuf;
+      __diff_t _M_max = -1;
+      __diff_t _M_count = 0;
+      __diff_t _M_total = 0;
+      _Sink_state _M_state = _Sink_state::_S_stack;
+
+      void
+      _M_use_stackbuf()
+      {
+       this->_M_reset(_M_buf);
+       if (_M_state != _Sink_state::_S_failed)
+         _M_state = _Sink_state::_S_stack;
+      }
+
+    protected:
+      // Try to switch to the streambuf's put area for zero-copy writes.
+      bool
+      _M_use_put_area(size_t __n)
+      {
+       if (_M_has_failed())
+         return false;
+       if (auto __p = _M_sbuf->pptr())
+         if (auto __e = _M_sbuf->epptr();
+             __e && __e > __p
+             && static_cast<size_t>(__e - __p) >= __n)
+           {
+             size_t __avail = static_cast<size_t>(__e - __p);
+             if (_M_max >= 0)
+               {
+                 auto __limit = _M_max - _M_count;
+                 if (__limit <= 0
+                     || static_cast<size_t>(__limit) < __n)
+                   return false;
+                 if (__avail > static_cast<size_t>(__limit))
+                   __avail = static_cast<size_t>(__limit);
+               }
+             this->_M_reset(span<_CharT>{__p, __avail});
+             _M_state = _Sink_state::_S_put_area;
+             return true;
+           }
+       return false;
+      }
+
+      // Write __s to the streambuf via sputn (stack buffer)
+      // or pbump (put area), depending on _M_state.
+      void
+      _M_do_flush(span<_CharT> __s)
+      {
+       _M_total += __s.size();
+       if (_M_max >= 0)
+       {
+         auto __limit = _M_max - _M_count;
+         if (__limit <= 0)
+           return;
+         if (__s.size() > static_cast<size_t>(__limit))
+           __s = __s.first(static_cast<size_t>(__limit));
+       }
+
+       switch (_M_state)
+       {
+         case _Sink_state::_S_stack:
+           {
+             streamsize __written
+               = _M_sbuf->sputn(__s.data(), __s.size());
+             if (__written != __s.size()) [[unlikely]]
+               _M_state = _Sink_state::_S_failed;
+             _M_count += __written;
+           }
+           break;
+         case _Sink_state::_S_put_area:
+           _M_sbuf->__safe_pbump(__s.size());
+           _M_count += __s.size();
+           break;
+         case _Sink_state::_S_failed:
+           break;
+       }
+      }
+
+      void
+      _M_overflow() override
+      {
+       auto __s = this->_M_used();
+       if (!__s.empty()) [[likely]]
+         _M_do_flush(__s);
+       if (_M_max >= 0 && _M_count >= _M_max)
+         {
+           _M_use_stackbuf();
+           return;
+         }
+       if (!_M_use_put_area(0))
+         _M_use_stackbuf();
+      }
+
+      bool
+      _M_discarding() const override
+      {
+       return _M_state == _Sink_state::_S_failed
+         || (_M_max >= 0 && _M_count >= _M_max);
+      }
+
+    public:
+      explicit
+      _Streambuf_sink(basic_streambuf<_CharT, _Traits>* __sbuf,
+                     __diff_t __max = -1) noexcept
+      : _M_sbuf(__sbuf), _M_max(__max)
+      { }
+
+      using _Sink<_CharT>::out;
+
+      typename _Sink<_CharT>::_Reservation
+      _M_reserve(size_t __n) override
+      {
+       if (_M_has_failed())
+         return { nullptr };
+
+       if (!this->_M_used().empty())
+         {
+           if(__n <= this->_M_unused().size())
+             return { this };
+
+           this->_M_do_flush(this->_M_used());
+         }
+
+       // 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 };
+      }
+
+      bool
+      _M_has_failed() const noexcept
+      { return _M_state == _Sink_state::_S_failed; }
+
+      void
+      _M_set_failed() noexcept
+      { _M_state = _Sink_state::_S_failed; }
+
+      __diff_t
+      _M_total_chars() const noexcept
+      { return _M_total; }
+    };
+
   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

Reply via email to