From ecba8547dd398ad4b627756013dbd22be417d4da Mon Sep 17 00:00:00 2001 From: Jonathan Wakely Date: Wed, 11 Nov 2020 00:19:40 +0000 Subject: [PATCH] libstdc++: Implement std::emit_on_flush etc. This adds the manipulators for use with basic_osyncstream. In order to detect when an arbitrary basic_ostream is the base class of a basic_syncbuf object, introduce a new intermediate base class that stores the data members. The new base class stores a pointer and two bools, which wastes (sizeof(void*) - 2) bytes of padding. It would be possible to use the two least significant bits of the pointer for the two bools, at least for targets where alignof(basic_streambuf) > 2, but that's left as a possible change for a future date. Also define basic_syncbuf::overflow to override the virtual function in the base class, so that single characters can be inserted into the stream buffer. Previously the default basic_streambuf::overflow implementation was used, which drops the character on the floor. libstdc++-v3/ChangeLog: * include/std/ostream (__syncbuf_base): New class template. (emit_on_flush, noemit_on_flush, flush_emit): New manipulators. * include/std/syncstream (basic_syncbuf): Derive from __syncbuf_base instead of basic_streambuf. (basic_syncbuf::operator=): Remove self-assignment check. (basic_syncbuf::swap): Remove self-swap check. (basic_syncbuf::emit): Do not skip pubsync() call if sequence is empty. (basic_syncbuf::sync): Remove no-op pubsync on stringbuf. (basic_syncbuf::overflow): Define override. * testsuite/27_io/basic_syncstream/basic_ops/1.cc: Test basic_osyncstream::put(char_type). * testsuite/27_io/basic_ostream/emit/1.cc: New test. --- libstdc++-v3/include/std/ostream | 67 ++++++++ libstdc++-v3/include/std/syncstream | 149 ++++++++---------- .../testsuite/27_io/basic_ostream/emit/1.cc | 44 ++++++ .../27_io/basic_syncstream/basic_ops/1.cc | 31 +++- 4 files changed, 210 insertions(+), 81 deletions(-) create mode 100644 libstdc++-v3/testsuite/27_io/basic_ostream/emit/1.cc diff --git a/libstdc++-v3/include/std/ostream b/libstdc++-v3/include/std/ostream index 9a80adf3a5a..c203e31d7c9 100644 --- a/libstdc++-v3/include/std/ostream +++ b/libstdc++-v3/include/std/ostream @@ -776,6 +776,73 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __ret_os << __x; return __ret_os; } + +#if __cplusplus > 201703L && _GLIBCXX_USE_CXX11_ABI + template + class __syncbuf_base : public basic_streambuf<_CharT, _Traits> + { + public: + static bool* + _S_get(basic_streambuf<_CharT, _Traits>* __buf) noexcept + { + if (auto __p = dynamic_cast<__syncbuf_base*>(__buf)) + return &__p->_M_emit_on_sync; + return nullptr; + } + + protected: + __syncbuf_base(basic_streambuf<_CharT, _Traits>* __w = nullptr) + : _M_wrapped(__w) + { } + + basic_streambuf<_CharT, _Traits>* _M_wrapped = nullptr; + bool _M_emit_on_sync = false; + bool _M_needs_sync = false; + }; + + template + inline basic_ostream<_CharT, _Traits>& + emit_on_flush(basic_ostream<_CharT, _Traits>& __os) + { + if (bool* __flag = __syncbuf_base<_CharT, _Traits>::_S_get(__os.rdbuf())) + *__flag = true; + return __os; + } + + template + inline basic_ostream<_CharT, _Traits>& + noemit_on_flush(basic_ostream<_CharT, _Traits>& __os) + { + if (bool* __flag = __syncbuf_base<_CharT, _Traits>::_S_get(__os.rdbuf())) + *__flag = false; + return __os; + } + + template + inline basic_ostream<_CharT, _Traits>& + flush_emit(basic_ostream<_CharT, _Traits>& __os) + { + struct _Restore + { + ~_Restore() { *_M_flag = _M_prev; } + + bool _M_prev = false; + bool* _M_flag = &_M_prev; + } __restore; + + if (bool* __flag = __syncbuf_base<_CharT, _Traits>::_S_get(__os.rdbuf())) + { + __restore._M_prev = *__flag; + __restore._M_flag = __flag; + *__flag = true; + } + + __os.flush(); + return __os; + } + +#endif // C++20 + #endif // C++11 _GLIBCXX_END_NAMESPACE_VERSION diff --git a/libstdc++-v3/include/std/syncstream b/libstdc++-v3/include/std/syncstream index 9d1db0cf286..07aab65223e 100644 --- a/libstdc++-v3/include/std/syncstream +++ b/libstdc++-v3/include/std/syncstream @@ -52,7 +52,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template, typename _Alloc = allocator<_CharT>> - class basic_syncbuf : public basic_streambuf<_CharT, _Traits> + class basic_syncbuf : public __syncbuf_base<_CharT, _Traits> { public: using char_type = _CharT; @@ -69,22 +69,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION explicit basic_syncbuf(streambuf_type* __obuf) - : basic_syncbuf(__obuf, allocator_type{}) + : basic_syncbuf(__obuf, allocator_type{}) { } basic_syncbuf(streambuf_type* __obuf, const allocator_type& __alloc) - : _M_wrapped(__obuf) - , _M_impl(__alloc) - , _M_mtx(__obuf) + : __syncbuf_base<_CharT, _Traits>(__obuf) + , _M_impl(__alloc) + , _M_mtx(__obuf) { } basic_syncbuf(basic_syncbuf&& __other) - : _M_wrapped(__other._M_wrapped) - , _M_impl(std::move(__other._M_impl)) - , _M_mtx(std::move(__other._M_mtx)) - , _M_emit_on_sync(__other._M_emit_on_sync) - , _M_needs_sync(__other._M_needs_sync) + : __syncbuf_base<_CharT, _Traits>(__other._M_wrapped) + , _M_impl(std::move(__other._M_impl)) + , _M_mtx(std::move(__other._M_mtx)) { + this->_M_emit_on_sync = __other._M_emit_on_sync; + this->_M_needs_sync = __other._M_needs_sync; __other._M_wrapped = nullptr; } @@ -98,82 +98,93 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { } } - basic_syncbuf& operator=(basic_syncbuf&& __other) + basic_syncbuf& + operator=(basic_syncbuf&& __other) { - if (std::__addressof(__other) != this) - { - emit(); + emit(); + + _M_impl = std::move(__other._M_impl); + this->_M_emit_on_sync = __other._M_emit_on_sync; + this->_M_needs_sync = __other._M_needs_sync; + this->_M_wrapped = __other._M_wrapped; + __other._M_wrapped = nullptr; + _M_mtx = std::move(__other._M_mtx); - _M_impl = std::move(__other._M_impl); - _M_wrapped = __other._M_wrapped; __other._M_wrapped = nullptr; - _M_mtx = std::move(__other._M_mtx); - _M_emit_on_sync = __other._M_emit_on_sync; - _M_needs_sync = __other._M_needs_sync; - } return *this; } void - swap(basic_syncbuf& __other) + swap(basic_syncbuf& __other) noexcept { - if (std::__addressof(__other) != this) - { - std::swap(_M_impl, __other._M_impl); - std::swap(_M_wrapped, __other._M_wrapped); - std::swap(_M_mtx, __other._M_mtx); - std::swap(_M_emit_on_sync, __other._M_emit_on_sync); - std::swap(_M_needs_sync, __other._M_needs_sync); - } + using _ATr = allocator_traits<_Alloc>; + if constexpr (!_ATr::propagate_on_container_swap::value) + __glibcxx_assert(get_allocator() == __other.get_allocator()); + + std::swap(_M_impl, __other._M_impl); + std::swap(this->_M_emit_on_sync, __other._M_emit_on_sync); + std::swap(this->_M_needs_sync, __other._M_needs_sync); + std::swap(this->_M_wrapped, __other._M_wrapped); + std::swap(_M_mtx, __other._M_mtx); } bool emit() { - if (!_M_wrapped) + if (!this->_M_wrapped) return false; - auto __s = _M_impl.view(); - if (__s.empty()) - return true; + auto __s = std::move(_M_impl).str(); const lock_guard<__mutex> __l(_M_mtx); - if (_M_wrapped->sputn(__s.data(), __s.size()) != __s.size()) - return false; + if (auto __size = __s.size()) + { + auto __n = this->_M_wrapped->sputn(__s.data(), __size); + if (__n != __size) + { + __s.erase(0, __n); + _M_impl.str(std::move(__s)); + return false; + } + } - if (_M_needs_sync) + if (this->_M_needs_sync) { - _M_needs_sync = false; - if (_M_wrapped->pubsync() != 0) + this->_M_needs_sync = false; + if (this->_M_wrapped->pubsync() != 0) return false; } - - _M_impl.str(""); return true; } streambuf_type* get_wrapped() const noexcept - { return _M_wrapped; } + { return this->_M_wrapped; } - allocator_type get_allocator() const noexcept + allocator_type + get_allocator() const noexcept { return _M_impl.get_allocator(); } void set_emit_on_sync(bool __b) noexcept - { _M_emit_on_sync = __b; } + { this->_M_emit_on_sync = __b; } protected: int sync() override { - auto __res = _M_impl.pubsync(); - if (__res == 0) - { - _M_needs_sync = true; - if (_M_emit_on_sync) - return emit() ? 0 : -1; - } - return __res; + this->_M_needs_sync = true; + if (this->_M_emit_on_sync && !emit()) + return -1; + return 0; + } + + int_type + overflow(int_type __c) override + { + int_type __eof = traits_type::eof(); + if (__builtin_expect(!traits_type::eq_int_type(__c, __eof), true)) + return _M_impl.sputc(__c); + return __eof; } streamsize @@ -181,11 +192,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { return _M_impl.sputn(__s, __n); } private: - streambuf_type* _M_wrapped; - - using __impl_type = basic_stringbuf; - __impl_type _M_impl; + basic_stringbuf _M_impl; struct __mutex { @@ -203,15 +210,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION void lock() { - if (_M_mtx) - _M_mtx->lock(); + _M_mtx->lock(); } void unlock() { - if (_M_mtx) - _M_mtx->unlock(); + _M_mtx->unlock(); } // FIXME: This should be put in the .so @@ -225,31 +230,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return __m[__key]; } #else - __mutex(void*) - { } - - void - swap(__mutex&&) noexcept - { } - - void - lock() - { } - - void - unlock() - { } + __mutex(void*) { } + void swap(__mutex&&) noexcept { } + void lock() { } + void unlock() { } #endif - __mutex(const __mutex&) = delete; - __mutex& operator=(const __mutex&) = delete; - __mutex(__mutex&&) = default; __mutex& operator=(__mutex&&) = default; }; __mutex _M_mtx; - - bool _M_emit_on_sync = false; - bool _M_needs_sync = false; }; template , diff --git a/libstdc++-v3/testsuite/27_io/basic_ostream/emit/1.cc b/libstdc++-v3/testsuite/27_io/basic_ostream/emit/1.cc new file mode 100644 index 00000000000..c50648adf63 --- /dev/null +++ b/libstdc++-v3/testsuite/27_io/basic_ostream/emit/1.cc @@ -0,0 +1,44 @@ +// Copyright (C) 2020 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// { dg-options "-std=gnu++2a" } +// { dg-additional-options "-pthread" { target pthread } } +// { dg-do run { target c++2a } } +// { dg-require-effective-target cxx11-abi } + +#include +#include + +void +test01() +{ + std::stringbuf sb; + std::osyncstream s(&sb); + s << "abc" << std::emit_on_flush << "def" << std::flush << "ghi" + << std::emit_on_flush << std::noemit_on_flush << std::endl; + VERIFY( sb.view() == "abcdef" ); + s << "jkl" << std::flush_emit << "mno" << std::flush; + VERIFY( sb.view() == "abcdefghi\njkl" ); + s.emit(); + VERIFY( sb.view() == "abcdefghi\njklmno" ); +} + +int +main() +{ + test01(); +} diff --git a/libstdc++-v3/testsuite/27_io/basic_syncstream/basic_ops/1.cc b/libstdc++-v3/testsuite/27_io/basic_syncstream/basic_ops/1.cc index ef463996b31..3ca97aa0c5b 100644 --- a/libstdc++-v3/testsuite/27_io/basic_syncstream/basic_ops/1.cc +++ b/libstdc++-v3/testsuite/27_io/basic_syncstream/basic_ops/1.cc @@ -123,12 +123,41 @@ test04() // emitting s.emit(); VERIFY( b.str() == txt ); } + + { + std::stringbuf b; + std::osyncstream s(&b); + + s.put('a'); + s.put('b'); + s.put('c'); + + s.emit(); + VERIFY( b.str() == "abc" ); + } + + { + std::stringbuf b; + std::osyncstream s(&b); + + s << "abc"; + s.put(' '); + s << "def"; + s.emit(); + VERIFY( b.str() == "abc def" ); + + s << "ghi"; + s.put(' '); + s << "jkl"; + s.emit(); + VERIFY( b.str() == "abc defghi jkl" ); + } } + int main() { test01(); test02(); test03(); test04(); - return 0; } -- 2.30.2