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() member.
* 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 | 7 ++
libstdc++-v3/include/std/format | 107 ++++++++++++++++++
libstdc++-v3/include/std/streambuf | 4 +
3 files changed, 118 insertions(+)
diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h
b/libstdc++-v3/include/bits/streambuf_iterator.h
index 095928ca4..564365934 100644
--- a/libstdc++-v3/include/bits/streambuf_iterator.h
+++ b/libstdc++-v3/include/bits/streambuf_iterator.h
@@ -318,6 +318,13 @@ _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; }
+ /// @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..cb6cc4592 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,112 @@ 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:
+ enum class _Sink_state { _S_stack, _S_put_area };
+
+ basic_streambuf<_CharT, _Traits>* _M_sbuf;
+ _Sink_state _M_state = _Sink_state::_S_stack;
+
+ // Switch to _M_buf when the streambuf has no put area
+ // or we need to stop using it.
+ void
+ _M_use_stackbuf()
+ {
+ this->_M_reset(_M_buf);
+ _M_state = _Sink_state::_S_stack;
+ }
+
+ // 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_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)
+ {
+ this->_M_reset(
+ span<_CharT>{__p, static_cast<size_t>(__e - __p)});
+ _M_state = _Sink_state::_S_put_area;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Allow derived classes to commit to the put area
+ // without needing friendship to basic_streambuf.
+ void
+ _M_pbump(streamsize __n)
+ { _M_sbuf->__safe_pbump(__n); }
+
+ _GLIBCXX_CONSTEXPR_FORMAT void
+ _M_overflow() override
+ {
+ auto __s = this->_M_used();
+ if (__s.empty()) [[unlikely]]
+ return;
+
+ switch (_M_state)
+ {
+ case _Sink_state::_S_stack:
+ // _Iter_sink overrides _M_overflow and calls
+ // _M_out._M_put(), which checks sputn's return
+ // value and tracks failure on the iterator.
+ _M_sbuf->sputn(__s.data(), __s.size());
+ break;
+ case _Sink_state::_S_put_area:
+ _M_sbuf->__safe_pbump(__s.size());
+ break;
+ }
+
+ if (!_M_use_put_area())
+ _M_use_stackbuf();
+ }
+
+ public:
+ [[__gnu__::__always_inline__]]
+ constexpr explicit
+ _Streambuf_sink(basic_streambuf<_CharT, _Traits>* __sbuf) noexcept
+ : _M_sbuf(__sbuf)
+ { }
+
+ using _Sink<_CharT>::out;
+
+ _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 };
+ }
+ };
+
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