From f51e83bf17f916c7e87f594ec48f54ec67ddf9ac Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 2 Oct 2023 23:20:25 +0300 Subject: [PATCH 01/10] stack can store nontrivial types --- include/boost/json/detail/impl/stack.hpp | 176 +++++++++++++++++++++++ include/boost/json/detail/impl/stack.ipp | 62 +++++++- include/boost/json/detail/stack.hpp | 75 +++++----- 3 files changed, 270 insertions(+), 43 deletions(-) create mode 100644 include/boost/json/detail/impl/stack.hpp diff --git a/include/boost/json/detail/impl/stack.hpp b/include/boost/json/detail/impl/stack.hpp new file mode 100644 index 000000000..75819edc3 --- /dev/null +++ b/include/boost/json/detail/impl/stack.hpp @@ -0,0 +1,176 @@ +// +// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/json +// + +#ifndef BOOST_JSON_DETAIL_IMPL_STACK_HPP +#define BOOST_JSON_DETAIL_IMPL_STACK_HPP + +#include +#include + +namespace boost { +namespace json { +namespace detail { + +//-------------------------------------- +/* + We put the non-trivial objects at + the lo end of the buffer and the + trivial objects at the hi end: + + base_ {free} base_+cap_ + |<------>|<------>|<------>| + size0_ size1_ +*/ +//-------------------------------------- + +struct stack::non_trivial +{ + non_trivial* next; + + virtual BOOST_JSON_DECL ~non_trivial() = 0; + virtual non_trivial* copy(void*) noexcept = 0; + virtual void* get() = 0; +}; + +template +void +stack:: +push_unchecked(T const& t) +{ + BOOST_STATIC_ASSERT( + std::is_trivial::value); + BOOST_ASSERT( + sizeof(T) <= cap_ - ( + size0_ + size1_)); + size1_ += sizeof(T); + std::memcpy( + base_ + cap_ - size1_, + &t, + sizeof(T)); +} + +template +void +stack:: +peek(T& t) +{ + BOOST_STATIC_ASSERT( + std::is_trivial::value); + BOOST_ASSERT( + size1_ >= sizeof(T)); + std::memcpy( + &t, + base_ + cap_ - size1_, + sizeof(T)); +} + +//-------------------------------------- + +// trivial +template +void +stack:: +push(T const& t, std::true_type) +{ + if(sizeof(T) > cap_ - ( + size0_ + size1_)) + reserve_impl( + size0_ + + sizeof(T) + + size1_); + push_unchecked(t); +} + +// non-trivial +template +void +stack:: +push( + T const& t, + std::false_type) +{ + struct alignas(core::max_align_t) + U : non_trivial + { + T t_; + + explicit U(T const& t) + : t_(t) + { + } + + non_trivial* + copy(void* dest) noexcept override + { + return (::new(dest) + U(t_)) + 1; + } + + void* + get() override + { + return &t_; + } + }; + BOOST_STATIC_ASSERT( + ! std::is_trivial::value); + BOOST_ASSERT( + alignof(U) <= alignof( + core::max_align_t)); + BOOST_ASSERT((sizeof(U) % alignof( + core::max_align_t)) == 0); + if(sizeof(U) > cap_ - ( + size0_ + size1_)) + reserve_impl( + size0_ + + sizeof(U) + + size1_); + auto nt = ::new( + base_ + size0_) U(t); + nt->next = head_; + head_ = nt; + size0_ += sizeof(U); +} + +// trivial +template +void +stack:: +pop(T& t, std::true_type) +{ + BOOST_ASSERT( + size1_ >= sizeof(T)); + peek(t); + size1_ -= sizeof(T); +} + +// non-trivial +template +void +stack:: +pop(T& t, std::false_type) +{ + t = std::move(*reinterpret_cast< + T*>(head_->get())); + auto next = head_->next; + head_->~non_trivial(); + head_ = next; + if(head_) + size0_ = reinterpret_cast< + unsigned char const*>(head_) - + base_; + else + size0_ = 0; +} + +} // detail +} // json +} // boost + +#endif diff --git a/include/boost/json/detail/impl/stack.ipp b/include/boost/json/detail/impl/stack.ipp index ab0467821..2eb035f2a 100644 --- a/include/boost/json/detail/impl/stack.ipp +++ b/include/boost/json/detail/impl/stack.ipp @@ -16,9 +16,14 @@ namespace boost { namespace json { namespace detail { +stack:: +non_trivial:: +~non_trivial() = default; + stack:: ~stack() { + clear(); if(base_ != buf_) sp_->deallocate( base_, cap_); @@ -38,16 +43,63 @@ stack( void stack:: -reserve(std::size_t n) +clear() noexcept +{ + while(head_) + { + auto const next = head_->next; + head_->~non_trivial(); + head_ = next; + } + size0_ = 0; + size1_ = 0; +} + +void +stack:: +reserve_impl( + std::size_t n) { - if(cap_ >= n) - return; + // caller checks this + BOOST_ASSERT(n > cap_); + auto const base = static_cast< unsigned char*>(sp_->allocate(n)); if(base_) { - if(size_ > 0) - std::memcpy(base, base_, size_); + // copy trivials + if(size1_ > 0) + std::memcpy( + base + n - size1_, + base_ + cap_ - size1_, + size1_); + + // copy non-trivials + if(head_) + { + non_trivial* head = nullptr; + auto dest = reinterpret_cast< + non_trivial*>(base); + auto next = head_->next; + auto prev = dest; + dest = head_->copy(dest); + prev->next = head; + head = prev; + head_->~non_trivial(); + head_ = next; + while(head_) + { + next = head_->next; + prev = dest; + dest = head_->copy(dest); + prev->next = head; + head = prev; + head_->~non_trivial(); + head_ = next; + } + head_ = head; + } + if(base_ != buf_) sp_->deallocate(base_, cap_); } diff --git a/include/boost/json/detail/stack.hpp b/include/boost/json/detail/stack.hpp index be2a6137b..c509e1b91 100644 --- a/include/boost/json/detail/stack.hpp +++ b/include/boost/json/detail/stack.hpp @@ -12,7 +12,9 @@ #include #include +#include #include +#include namespace boost { namespace json { @@ -20,9 +22,13 @@ namespace detail { class stack { + struct non_trivial; + storage_ptr sp_; std::size_t cap_ = 0; - std::size_t size_ = 0; + std::size_t size0_ = 0; // lo stack + std::size_t size1_ = 0; // hi stack + non_trivial* head_ = nullptr; unsigned char* base_ = nullptr; unsigned char* buf_ = nullptr; @@ -40,70 +46,63 @@ class stack bool empty() const noexcept { - return size_ == 0; + return size0_ + size1_ == 0; } + BOOST_JSON_DECL void - clear() noexcept - { - size_ = 0; - } + clear() noexcept; - BOOST_JSON_DECL void - reserve(std::size_t n); + reserve(std::size_t n) + { + if(n > cap_) + reserve_impl(n); + } template void push(T const& t) { - auto const n = sizeof(T); - // If this assert goes off, it - // means the calling code did not - // reserve enough to prevent a - // reallocation. - //BOOST_ASSERT(cap_ >= size_ + n); - reserve(size_ + n); - std::memcpy( - base_ + size_, &t, n); - size_ += n; + push(t, std::is_trivial{}); } template void - push_unchecked(T const& t) - { - auto const n = sizeof(T); - BOOST_ASSERT(size_ + n <= cap_); - std::memcpy( - base_ + size_, &t, n); - size_ += n; - } + push_unchecked( + T const& t); template void - peek(T& t) - { - auto const n = sizeof(T); - BOOST_ASSERT(size_ >= n); - std::memcpy(&t, - base_ + size_ - n, n); - } + peek(T& t); template void pop(T& t) { - auto const n = sizeof(T); - BOOST_ASSERT(size_ >= n); - size_ -= n; - std::memcpy( - &t, base_ + size_, n); + pop(t, std::is_trivial{}); } + +private: + template void push( + T const& t, std::true_type); + template void push( + T const& t, std::false_type); + template void pop( + T& t, std::true_type); + template void pop( + T& t, std::false_type); + + BOOST_JSON_DECL + void + reserve_impl( + std::size_t n); }; } // detail } // namespace json } // namespace boost +#include + #endif From 82a5e0a92b264bc4c79e132aabf6d4709e509d82 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Tue, 3 Oct 2023 12:09:12 +0300 Subject: [PATCH 02/10] detail::stack tests --- test/serializer.cpp | 74 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/test/serializer.cpp b/test/serializer.cpp index 034b75194..c266c00c7 100644 --- a/test/serializer.cpp +++ b/test/serializer.cpp @@ -10,10 +10,10 @@ // Test that header file is self-contained. #include +#include #include #include #include -#include #include #include "parse-vectors.hpp" @@ -592,6 +592,77 @@ class serializer_test BOOST_TEST(serialize(parse("-0.0")) == "-0E0"); } + void + testStack() + { + char const* sample = "sample string"; + + detail::stack st; + BOOST_TEST( st.empty() ); + + st.push( 1 ); + BOOST_TEST( !st.empty() ); + st.push( sample ); + st.push( 3.4 ); + + std::vector v{1, 2, 3, 4, 5}; + st.push(v); + + v.pop_back(); + st.push(v); + + { + std::vector v1; + st.pop( v1 ); + + BOOST_TEST( v == v1 ); + } + v.push_back(5); + { + std::vector v1; + st.pop( v1 ); + + BOOST_TEST( v == v1 ); + } + { + double d1; + st.peek( d1 ); + BOOST_TEST( d1 == 3.4 ); + + double d2; + st.pop( d2 ); + BOOST_TEST( d2 == d1 ); + BOOST_TEST( !st.empty() ); + } + { + char const* s1; + st.peek( s1 ); + BOOST_TEST( s1 == sample ); + + char const* s2; + st.pop( s2 ); + BOOST_TEST( s2 == s1 ); + BOOST_TEST( !st.empty() ); + } + { + int n1; + st.peek( n1 ); + BOOST_TEST( n1 == 1 ); + + int n2; + st.pop( n2 ); + BOOST_TEST( n2 == n1 ); + BOOST_TEST( st.empty() ); + } + + st.clear(); + BOOST_TEST( st.empty() ); + + st.push( 1 ); + st.clear(); + BOOST_TEST( st.empty() ); + } + void run() { @@ -606,6 +677,7 @@ class serializer_test testVectors(); testOstream(); testNumberRoundTrips(); + testStack(); } }; From 65fa0b87f17a5f0d07d1693487ddfe5296f2ce4e Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Mon, 23 Oct 2023 20:49:36 +0300 Subject: [PATCH 03/10] require trivial copy for fast stack insertion --- include/boost/json/detail/impl/stack.hpp | 10 ++++------ include/boost/json/detail/stack.hpp | 21 ++++++++++++++++++--- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/include/boost/json/detail/impl/stack.hpp b/include/boost/json/detail/impl/stack.hpp index 75819edc3..6614735b3 100644 --- a/include/boost/json/detail/impl/stack.hpp +++ b/include/boost/json/detail/impl/stack.hpp @@ -11,6 +11,7 @@ #define BOOST_JSON_DETAIL_IMPL_STACK_HPP #include +#include #include namespace boost { @@ -43,8 +44,7 @@ void stack:: push_unchecked(T const& t) { - BOOST_STATIC_ASSERT( - std::is_trivial::value); + BOOST_STATIC_ASSERT( is_trivially_copy_assignable::value ); BOOST_ASSERT( sizeof(T) <= cap_ - ( size0_ + size1_)); @@ -60,8 +60,7 @@ void stack:: peek(T& t) { - BOOST_STATIC_ASSERT( - std::is_trivial::value); + BOOST_STATIC_ASSERT( is_trivially_copy_assignable::value ); BOOST_ASSERT( size1_ >= sizeof(T)); std::memcpy( @@ -118,8 +117,7 @@ push( return &t_; } }; - BOOST_STATIC_ASSERT( - ! std::is_trivial::value); + BOOST_STATIC_ASSERT( ! is_trivially_copy_assignable::value ); BOOST_ASSERT( alignof(U) <= alignof( core::max_align_t)); diff --git a/include/boost/json/detail/stack.hpp b/include/boost/json/detail/stack.hpp index c509e1b91..29bc13f09 100644 --- a/include/boost/json/detail/stack.hpp +++ b/include/boost/json/detail/stack.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -20,6 +20,21 @@ namespace boost { namespace json { namespace detail { +#if defined( BOOST_LIBSTDCXX_VERSION ) && BOOST_LIBSTDCXX_VERSION < 50000 + +template +struct is_trivially_copy_assignable + : mp11::mp_bool< + std::is_copy_assignable::value && + std::has_trivial_copy_assign::value > +{}; + +#else + +using std::is_trivially_copy_assignable; + +#endif + class stack { struct non_trivial; @@ -64,7 +79,7 @@ class stack void push(T const& t) { - push(t, std::is_trivial{}); + push( t, is_trivially_copy_assignable() ); } template @@ -80,7 +95,7 @@ class stack void pop(T& t) { - pop(t, std::is_trivial{}); + pop( t, is_trivially_copy_assignable() ); } private: From 873d188f60a3d54c2b4157562fb933b4725f6d23 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Tue, 3 Oct 2023 12:09:33 +0300 Subject: [PATCH 04/10] inlining detail::stack --- include/boost/json/detail/impl/stack.hpp | 87 ++++++++++++++++++++++++ include/boost/json/detail/impl/stack.ipp | 87 ------------------------ include/boost/json/detail/stack.hpp | 8 ++- 3 files changed, 93 insertions(+), 89 deletions(-) diff --git a/include/boost/json/detail/impl/stack.hpp b/include/boost/json/detail/impl/stack.hpp index 6614735b3..f2fb89b30 100644 --- a/include/boost/json/detail/impl/stack.hpp +++ b/include/boost/json/detail/impl/stack.hpp @@ -39,6 +39,58 @@ struct stack::non_trivial virtual void* get() = 0; }; +void +stack:: +reserve_impl( + std::size_t n) +{ + // caller checks this + BOOST_ASSERT(n > cap_); + + auto const base = static_cast< + unsigned char*>(sp_->allocate(n)); + if(base_) + { + // copy trivials + if(size1_ > 0) + std::memcpy( + base + n - size1_, + base_ + cap_ - size1_, + size1_); + + // copy non-trivials + if(head_) + { + non_trivial* head = nullptr; + auto dest = reinterpret_cast< + non_trivial*>(base); + auto next = head_->next; + auto prev = dest; + dest = head_->copy(dest); + prev->next = head; + head = prev; + head_->~non_trivial(); + head_ = next; + while(head_) + { + next = head_->next; + prev = dest; + dest = head_->copy(dest); + prev->next = head; + head = prev; + head_->~non_trivial(); + head_ = next; + } + head_ = head; + } + + if(base_ != buf_) + sp_->deallocate(base_, cap_); + } + base_ = base; + cap_ = n; +} + template void stack:: @@ -167,6 +219,41 @@ pop(T& t, std::false_type) size0_ = 0; } +void +stack:: +clear() noexcept +{ + while(head_) + { + auto const next = head_->next; + head_->~non_trivial(); + head_ = next; + } + size0_ = 0; + size1_ = 0; +} + +stack:: +~stack() +{ + clear(); + if(base_ != buf_) + sp_->deallocate( + base_, cap_); +} + +stack:: +stack( + storage_ptr sp, + unsigned char* buf, + std::size_t buf_size) noexcept + : sp_(std::move(sp)) + , cap_(buf_size) + , base_(buf) + , buf_(buf) +{ +} + } // detail } // json } // boost diff --git a/include/boost/json/detail/impl/stack.ipp b/include/boost/json/detail/impl/stack.ipp index 2eb035f2a..f3e6fdb62 100644 --- a/include/boost/json/detail/impl/stack.ipp +++ b/include/boost/json/detail/impl/stack.ipp @@ -20,93 +20,6 @@ stack:: non_trivial:: ~non_trivial() = default; -stack:: -~stack() -{ - clear(); - if(base_ != buf_) - sp_->deallocate( - base_, cap_); -} - -stack:: -stack( - storage_ptr sp, - unsigned char* buf, - std::size_t buf_size) noexcept - : sp_(std::move(sp)) - , cap_(buf_size) - , base_(buf) - , buf_(buf) -{ -} - -void -stack:: -clear() noexcept -{ - while(head_) - { - auto const next = head_->next; - head_->~non_trivial(); - head_ = next; - } - size0_ = 0; - size1_ = 0; -} - -void -stack:: -reserve_impl( - std::size_t n) -{ - // caller checks this - BOOST_ASSERT(n > cap_); - - auto const base = static_cast< - unsigned char*>(sp_->allocate(n)); - if(base_) - { - // copy trivials - if(size1_ > 0) - std::memcpy( - base + n - size1_, - base_ + cap_ - size1_, - size1_); - - // copy non-trivials - if(head_) - { - non_trivial* head = nullptr; - auto dest = reinterpret_cast< - non_trivial*>(base); - auto next = head_->next; - auto prev = dest; - dest = head_->copy(dest); - prev->next = head; - head = prev; - head_->~non_trivial(); - head_ = next; - while(head_) - { - next = head_->next; - prev = dest; - dest = head_->copy(dest); - prev->next = head; - head = prev; - head_->~non_trivial(); - head_ = next; - } - head_ = head; - } - - if(base_ != buf_) - sp_->deallocate(base_, cap_); - } - base_ = base; - cap_ = n; -} - } // detail } // namespace json } // namespace boost diff --git a/include/boost/json/detail/stack.hpp b/include/boost/json/detail/stack.hpp index 29bc13f09..78237a72d 100644 --- a/include/boost/json/detail/stack.hpp +++ b/include/boost/json/detail/stack.hpp @@ -48,11 +48,13 @@ class stack unsigned char* buf_ = nullptr; public: - BOOST_JSON_DECL + // BOOST_JSON_DECL + inline ~stack(); stack() = default; + inline stack( storage_ptr sp, unsigned char* buf, @@ -64,7 +66,8 @@ class stack return size0_ + size1_ == 0; } - BOOST_JSON_DECL + // BOOST_JSON_DECL + inline void clear() noexcept; @@ -109,6 +112,7 @@ class stack T& t, std::false_type); BOOST_JSON_DECL + inline void reserve_impl( std::size_t n); From 9120e92b8ce4ea6876fb3b2cfab0a213f4211a37 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Tue, 10 Oct 2023 21:55:54 +0300 Subject: [PATCH 05/10] detail::stack places non-trivials and trivials in the same area --- include/boost/json/detail/impl/stack.hpp | 185 ++++++++++------------- include/boost/json/detail/stack.hpp | 8 +- test/serializer.cpp | 25 ++- 3 files changed, 104 insertions(+), 114 deletions(-) diff --git a/include/boost/json/detail/impl/stack.hpp b/include/boost/json/detail/impl/stack.hpp index f2fb89b30..a7e1c37b1 100644 --- a/include/boost/json/detail/impl/stack.hpp +++ b/include/boost/json/detail/impl/stack.hpp @@ -10,79 +10,63 @@ #ifndef BOOST_JSON_DETAIL_IMPL_STACK_HPP #define BOOST_JSON_DETAIL_IMPL_STACK_HPP -#include +#include #include -#include +#include +#include namespace boost { namespace json { namespace detail { -//-------------------------------------- -/* - We put the non-trivial objects at - the lo end of the buffer and the - trivial objects at the hi end: - - base_ {free} base_+cap_ - |<------>|<------>|<------>| - size0_ size1_ -*/ -//-------------------------------------- - struct stack::non_trivial { non_trivial* next; + std::size_t offset; virtual BOOST_JSON_DECL ~non_trivial() = 0; - virtual non_trivial* copy(void*) noexcept = 0; + virtual non_trivial* relocate(void*) noexcept = 0; virtual void* get() = 0; }; void stack:: -reserve_impl( - std::size_t n) +reserve_impl(std::size_t n) { // caller checks this BOOST_ASSERT(n > cap_); - auto const base = static_cast< - unsigned char*>(sp_->allocate(n)); + auto const base = static_cast( sp_->allocate(n) ); if(base_) { // copy trivials - if(size1_ > 0) - std::memcpy( - base + n - size1_, - base_ + cap_ - size1_, - size1_); + std::memcpy(base, base_, size_); // copy non-trivials - if(head_) + non_trivial* src = head_; + non_trivial* prev = nullptr; + non_trivial* head = nullptr; + while(src) { - non_trivial* head = nullptr; - auto dest = reinterpret_cast< - non_trivial*>(base); - auto next = head_->next; - auto prev = dest; - dest = head_->copy(dest); - prev->next = head; - head = prev; - head_->~non_trivial(); - head_ = next; - while(head_) - { - next = head_->next; - prev = dest; - dest = head_->copy(dest); - prev->next = head; - head = prev; - head_->~non_trivial(); - head_ = next; - } - head_ = head; + auto const next = src->next; + auto const offset = src->offset; + + std::size_t const buf_offset = + reinterpret_cast(src) - base_; + auto dest = reinterpret_cast( + src->relocate(base + buf_offset) ); + dest->offset = offset; + dest->next = nullptr; + + if( prev ) + prev->next = dest; + else + head = dest; + + prev = dest; + src = next; } + head_ = head; if(base_ != buf_) sp_->deallocate(base_, cap_); @@ -96,15 +80,11 @@ void stack:: push_unchecked(T const& t) { + constexpr std::size_t n = sizeof(T); BOOST_STATIC_ASSERT( is_trivially_copy_assignable::value ); - BOOST_ASSERT( - sizeof(T) <= cap_ - ( - size0_ + size1_)); - size1_ += sizeof(T); - std::memcpy( - base_ + cap_ - size1_, - &t, - sizeof(T)); + BOOST_ASSERT( n <= cap_ - size_ ); + std::memcpy( base_ + size_, &t, n ); + size_ += n; } template @@ -112,13 +92,10 @@ void stack:: peek(T& t) { + constexpr std::size_t n = sizeof(T); BOOST_STATIC_ASSERT( is_trivially_copy_assignable::value ); - BOOST_ASSERT( - size1_ >= sizeof(T)); - std::memcpy( - &t, - base_ + cap_ - size1_, - sizeof(T)); + BOOST_ASSERT( size_ >= n ); + std::memcpy( &t, base_ + size_ - n, n ); } //-------------------------------------- @@ -129,12 +106,9 @@ void stack:: push(T const& t, std::true_type) { - if(sizeof(T) > cap_ - ( - size0_ + size1_)) - reserve_impl( - size0_ + - sizeof(T) + - size1_); + if( sizeof(T) > cap_ - size_ ) + reserve_impl( sizeof(T) + size_ ); + push_unchecked(t); } @@ -142,12 +116,10 @@ push(T const& t, std::true_type) template void stack:: -push( - T const& t, - std::false_type) +push(T const& t, std::false_type) { - struct alignas(core::max_align_t) - U : non_trivial + struct U + : non_trivial { T t_; @@ -157,10 +129,11 @@ push( } non_trivial* - copy(void* dest) noexcept override + relocate(void* dest) noexcept override { - return (::new(dest) - U(t_)) + 1; + auto result = ::new(dest) U(t_); + this->~U(); + return result; } void* @@ -169,23 +142,37 @@ push( return &t_; } }; + BOOST_STATIC_ASSERT( ! is_trivially_copy_assignable::value ); + + constexpr std::size_t n = sizeof(U); + + void* ptr; + std::size_t offset; + do + { + std::size_t space = cap_ - size_; + unsigned char* buf = base_ + size_; + ptr = buf; + if( alignment::align(alignof(U), n, ptr, space) ) + { + offset = (reinterpret_cast(ptr) - buf) + n; + break; + } + + reserve_impl(size_ + n + alignof(U) - 1); + } + while(true); BOOST_ASSERT( - alignof(U) <= alignof( - core::max_align_t)); - BOOST_ASSERT((sizeof(U) % alignof( - core::max_align_t)) == 0); - if(sizeof(U) > cap_ - ( - size0_ + size1_)) - reserve_impl( - size0_ + - sizeof(U) + - size1_); - auto nt = ::new( - base_ + size0_) U(t); + (reinterpret_cast(ptr) + n - offset) == + (base_ + size_) ); + + auto nt = ::new(ptr) U(t); nt->next = head_; + nt->offset = offset; + head_ = nt; - size0_ += sizeof(U); + size_ += offset; } // trivial @@ -194,10 +181,9 @@ void stack:: pop(T& t, std::true_type) { - BOOST_ASSERT( - size1_ >= sizeof(T)); + BOOST_ASSERT( size_ >= sizeof(T) ); peek(t); - size1_ -= sizeof(T); + size_ -= sizeof(T); } // non-trivial @@ -206,17 +192,14 @@ void stack:: pop(T& t, std::false_type) { - t = std::move(*reinterpret_cast< - T*>(head_->get())); auto next = head_->next; + auto offset = head_->offset; + + t = std::move( *reinterpret_cast( head_->get() ) ); head_->~non_trivial(); + head_ = next; - if(head_) - size0_ = reinterpret_cast< - unsigned char const*>(head_) - - base_; - else - size0_ = 0; + size_ -= offset; } void @@ -229,8 +212,7 @@ clear() noexcept head_->~non_trivial(); head_ = next; } - size0_ = 0; - size1_ = 0; + size_ = 0; } stack:: @@ -238,8 +220,7 @@ stack:: { clear(); if(base_ != buf_) - sp_->deallocate( - base_, cap_); + sp_->deallocate(base_, cap_); } stack:: diff --git a/include/boost/json/detail/stack.hpp b/include/boost/json/detail/stack.hpp index 78237a72d..9a548b04f 100644 --- a/include/boost/json/detail/stack.hpp +++ b/include/boost/json/detail/stack.hpp @@ -41,14 +41,12 @@ class stack storage_ptr sp_; std::size_t cap_ = 0; - std::size_t size0_ = 0; // lo stack - std::size_t size1_ = 0; // hi stack + std::size_t size_ = 0; non_trivial* head_ = nullptr; unsigned char* base_ = nullptr; unsigned char* buf_ = nullptr; public: - // BOOST_JSON_DECL inline ~stack(); @@ -63,10 +61,9 @@ class stack bool empty() const noexcept { - return size0_ + size1_ == 0; + return size_ == 0; } - // BOOST_JSON_DECL inline void clear() noexcept; @@ -111,7 +108,6 @@ class stack template void pop( T& t, std::false_type); - BOOST_JSON_DECL inline void reserve_impl( diff --git a/test/serializer.cpp b/test/serializer.cpp index c266c00c7..d30dff364 100644 --- a/test/serializer.cpp +++ b/test/serializer.cpp @@ -600,10 +600,14 @@ class serializer_test detail::stack st; BOOST_TEST( st.empty() ); - st.push( 1 ); + st.clear(); + BOOST_TEST( st.empty() ); + + st.push(1); BOOST_TEST( !st.empty() ); - st.push( sample ); - st.push( 3.4 ); + + st.push(sample); + st.push(3.4); std::vector v{1, 2, 3, 4, 5}; st.push(v); @@ -611,6 +615,16 @@ class serializer_test v.pop_back(); st.push(v); + v.pop_back(); + st.push(v); + + { + std::vector v1; + st.pop( v1 ); + + BOOST_TEST( v == v1 ); + } + v.push_back(4); { std::vector v1; st.pop( v1 ); @@ -654,11 +668,10 @@ class serializer_test BOOST_TEST( n2 == n1 ); BOOST_TEST( st.empty() ); } - - st.clear(); BOOST_TEST( st.empty() ); - st.push( 1 ); + st.push(1); + st.push(v); st.clear(); BOOST_TEST( st.empty() ); } From ada771143f6205134d52cb1af031205ed9b82315 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Thu, 26 Oct 2023 19:27:01 +0300 Subject: [PATCH 06/10] implement moving into detail::stack --- include/boost/json/detail/impl/stack.hpp | 84 ++++++++++++------------ include/boost/json/detail/impl/stack.ipp | 2 +- include/boost/json/detail/stack.hpp | 13 ++-- 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/include/boost/json/detail/impl/stack.hpp b/include/boost/json/detail/impl/stack.hpp index a7e1c37b1..aaf5ffc12 100644 --- a/include/boost/json/detail/impl/stack.hpp +++ b/include/boost/json/detail/impl/stack.hpp @@ -19,14 +19,34 @@ namespace boost { namespace json { namespace detail { -struct stack::non_trivial +template<> +struct stack::non_trivial { non_trivial* next; std::size_t offset; virtual BOOST_JSON_DECL ~non_trivial() = 0; virtual non_trivial* relocate(void*) noexcept = 0; - virtual void* get() = 0; +}; + +template< class T > +struct stack::non_trivial + : stack::non_trivial +{ + T obj; + + explicit + non_trivial(T t) + : obj( std::move(t) ) + {} + + non_trivial<>* + relocate(void* dest) noexcept override + { + auto result = ::new(dest) non_trivial( std::move(obj) ); + this->~non_trivial(); + return result; + } }; void @@ -43,9 +63,9 @@ reserve_impl(std::size_t n) std::memcpy(base, base_, size_); // copy non-trivials - non_trivial* src = head_; - non_trivial* prev = nullptr; - non_trivial* head = nullptr; + non_trivial<>* src = head_; + non_trivial<>* prev = nullptr; + non_trivial<>* head = nullptr; while(src) { auto const next = src->next; @@ -53,8 +73,7 @@ reserve_impl(std::size_t n) std::size_t const buf_offset = reinterpret_cast(src) - base_; - auto dest = reinterpret_cast( - src->relocate(base + buf_offset) ); + non_trivial<>* dest = src->relocate(base + buf_offset); dest->offset = offset; dest->next = nullptr; @@ -116,36 +135,13 @@ push(T const& t, std::true_type) template void stack:: -push(T const& t, std::false_type) +push(T&& t, std::false_type) { - struct U - : non_trivial - { - T t_; - - explicit U(T const& t) - : t_(t) - { - } - - non_trivial* - relocate(void* dest) noexcept override - { - auto result = ::new(dest) U(t_); - this->~U(); - return result; - } - - void* - get() override - { - return &t_; - } - }; - BOOST_STATIC_ASSERT( ! is_trivially_copy_assignable::value ); - constexpr std::size_t n = sizeof(U); + using Holder = non_trivial< remove_cvref >; + constexpr std::size_t size = sizeof(Holder); + constexpr std::size_t alignment = alignof(Holder); void* ptr; std::size_t offset; @@ -154,20 +150,20 @@ push(T const& t, std::false_type) std::size_t space = cap_ - size_; unsigned char* buf = base_ + size_; ptr = buf; - if( alignment::align(alignof(U), n, ptr, space) ) + if( alignment::align(alignment, size, ptr, space) ) { - offset = (reinterpret_cast(ptr) - buf) + n; + offset = (reinterpret_cast(ptr) - buf) + size; break; } - reserve_impl(size_ + n + alignof(U) - 1); + reserve_impl(size_ + size + alignment - 1); } while(true); BOOST_ASSERT( - (reinterpret_cast(ptr) + n - offset) == + (reinterpret_cast(ptr) + size - offset) == (base_ + size_) ); - auto nt = ::new(ptr) U(t); + auto nt = ::new(ptr) Holder( static_cast(t) ); nt->next = head_; nt->offset = offset; @@ -195,8 +191,12 @@ pop(T& t, std::false_type) auto next = head_->next; auto offset = head_->offset; - t = std::move( *reinterpret_cast( head_->get() ) ); - head_->~non_trivial(); + using U = remove_cvref; + using Holder = non_trivial; + auto const head = static_cast(head_); + + t = std::move( head->obj ); + head->~Holder(); head_ = next; size_ -= offset; @@ -209,7 +209,7 @@ clear() noexcept while(head_) { auto const next = head_->next; - head_->~non_trivial(); + head_->~non_trivial<>(); head_ = next; } size_ = 0; diff --git a/include/boost/json/detail/impl/stack.ipp b/include/boost/json/detail/impl/stack.ipp index f3e6fdb62..3d122c627 100644 --- a/include/boost/json/detail/impl/stack.ipp +++ b/include/boost/json/detail/impl/stack.ipp @@ -17,7 +17,7 @@ namespace json { namespace detail { stack:: -non_trivial:: +non_trivial<>:: ~non_trivial() = default; } // detail diff --git a/include/boost/json/detail/stack.hpp b/include/boost/json/detail/stack.hpp index 9a548b04f..89c451da7 100644 --- a/include/boost/json/detail/stack.hpp +++ b/include/boost/json/detail/stack.hpp @@ -37,12 +37,13 @@ using std::is_trivially_copy_assignable; class stack { + template< class T = void > struct non_trivial; storage_ptr sp_; std::size_t cap_ = 0; std::size_t size_ = 0; - non_trivial* head_ = nullptr; + non_trivial<>* head_ = nullptr; unsigned char* base_ = nullptr; unsigned char* buf_ = nullptr; @@ -77,9 +78,10 @@ class stack template void - push(T const& t) + push(T&& t) { - push( t, is_trivially_copy_assignable() ); + using U = remove_cvref; + push( static_cast(t), is_trivially_copy_assignable() ); } template @@ -95,14 +97,15 @@ class stack void pop(T& t) { - pop( t, is_trivially_copy_assignable() ); + using U = remove_cvref; + pop( t, is_trivially_copy_assignable() ); } private: template void push( T const& t, std::true_type); template void push( - T const& t, std::false_type); + T&& t, std::false_type); template void pop( T& t, std::true_type); template void pop( From 0bcce498ee510a8330ee835c6ef5a09043e24e4f Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Mon, 11 Dec 2023 11:15:47 +0300 Subject: [PATCH 07/10] simplify source pointer storage in serializer --- include/boost/json/impl/serializer.ipp | 53 ++++++++++++++------------ include/boost/json/serializer.hpp | 10 +---- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/include/boost/json/impl/serializer.ipp b/include/boost/json/impl/serializer.ipp index a54d9171b..d41578d6b 100644 --- a/include/boost/json/impl/serializer.ipp +++ b/include/boost/json/impl/serializer.ipp @@ -399,10 +399,12 @@ bool serializer:: write_number(stream& ss0) { + BOOST_ASSERT( p_ ); + auto const pv = reinterpret_cast(p_); local_stream ss(ss0); if(StackEmpty || st_.empty()) { - switch(jv_->kind()) + switch(pv->kind()) { default: case kind::int64: @@ -411,11 +413,11 @@ write_number(stream& ss0) detail::max_number_chars)) { ss.advance(detail::format_int64( - ss.data(), jv_->get_int64())); + ss.data(), pv->get_int64())); return true; } cs0_ = { buf_, detail::format_int64( - buf_, jv_->get_int64()) }; + buf_, pv->get_int64()) }; break; case kind::uint64: @@ -424,11 +426,11 @@ write_number(stream& ss0) detail::max_number_chars)) { ss.advance(detail::format_uint64( - ss.data(), jv_->get_uint64())); + ss.data(), pv->get_uint64())); return true; } cs0_ = { buf_, detail::format_uint64( - buf_, jv_->get_uint64()) }; + buf_, pv->get_uint64()) }; break; case kind::double_: @@ -439,12 +441,12 @@ write_number(stream& ss0) ss.advance( detail::format_double( ss.data(), - jv_->get_double(), + pv->get_double(), opts_.allow_infinity_and_nan)); return true; } cs0_ = { buf_, detail::format_double( - buf_, jv_->get_double(), opts_.allow_infinity_and_nan) }; + buf_, pv->get_double(), opts_.allow_infinity_and_nan) }; break; } } @@ -478,7 +480,8 @@ write_array(stream& ss0) array::const_iterator end; if(StackEmpty || st_.empty()) { - pa = pa_; + BOOST_ASSERT( p_ ); + pa = reinterpret_cast(p_); it = pa->begin(); end = pa->end(); } @@ -510,7 +513,7 @@ do_arr1: for(;;) { do_arr2: - jv_ = &*it; + p_ = &*it; if(! write_value(ss)) return suspend( state::arr2, it, pa); @@ -544,7 +547,8 @@ write_object(stream& ss0) object::const_iterator end; if(StackEmpty || st_.empty()) { - po = po_; + BOOST_ASSERT( p_ ); + po = reinterpret_cast(p_); it = po->begin(); end = po->end(); } @@ -593,7 +597,7 @@ do_obj3: return suspend( state::obj3, it, po); do_obj4: - jv_ = &it->value(); + p_ = &it->value(); if(BOOST_JSON_UNLIKELY( ! write_value(ss))) return suspend( @@ -625,21 +629,22 @@ write_value(stream& ss) { if(StackEmpty || st_.empty()) { - auto const& jv(*jv_); - switch(jv.kind()) + BOOST_ASSERT( p_ ); + auto const pv = reinterpret_cast(p_); + switch(pv->kind()) { default: case kind::object: - po_ = &jv.get_object(); + p_ = &pv->get_object(); return write_object(ss); case kind::array: - pa_ = &jv.get_array(); + p_ = &pv->get_array(); return write_array(ss); case kind::string: { - auto const& js = jv.get_string(); + auto const& js = pv->get_string(); cs0_ = { js.data(), js.size() }; return write_string(ss); } @@ -650,7 +655,7 @@ write_value(stream& ss) return write_number(ss); case kind::bool_: - if(jv.get_bool()) + if(pv->get_bool()) { if(BOOST_JSON_LIKELY( ss.remain() >= 4)) @@ -743,7 +748,7 @@ read_some( if(st_.empty()) { done_ = true; - jv_ = nullptr; + p_ = nullptr; } return string_view( dest, ss.used(dest)); @@ -764,11 +769,9 @@ void serializer:: reset(value const* p) noexcept { - pv_ = p; + p_ = p; fn0_ = &serializer::write_value; fn1_ = &serializer::write_value; - - jv_ = p; st_.clear(); done_ = false; } @@ -777,7 +780,7 @@ void serializer:: reset(array const* p) noexcept { - pa_ = p; + p_ = p; fn0_ = &serializer::write_array; fn1_ = &serializer::write_array; st_.clear(); @@ -788,7 +791,7 @@ void serializer:: reset(object const* p) noexcept { - po_ = p; + p_ = p; fn0_ = &serializer::write_object; fn1_ = &serializer::write_object; st_.clear(); @@ -821,10 +824,10 @@ string_view serializer:: read(char* dest, std::size_t size) { - if(! jv_) + if(! p_) { static value const null; - jv_ = &null; + p_ = &null; } return read_some(dest, size); } diff --git a/include/boost/json/serializer.hpp b/include/boost/json/serializer.hpp index 7f4524a79..3b1eefa93 100644 --- a/include/boost/json/serializer.hpp +++ b/include/boost/json/serializer.hpp @@ -67,17 +67,9 @@ class serializer using fn_t = bool (serializer::*)(stream&); -#ifndef BOOST_JSON_DOCS - union - { - value const* pv_; - array const* pa_; - object const* po_; - }; -#endif + void const* p_ = nullptr; fn_t fn0_ = &serializer::write_null; fn_t fn1_ = &serializer::write_null; - value const* jv_ = nullptr; detail::stack st_; const_stream cs0_; serialize_options opts_; From d1bb1c4f0f61e959164fd4d927c63c2a9519eab7 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Mon, 11 Dec 2023 12:04:51 +0300 Subject: [PATCH 08/10] remove read_some in serializer --- include/boost/json/impl/serializer.ipp | 45 +++++++++++--------------- include/boost/json/serializer.hpp | 1 - 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/include/boost/json/impl/serializer.ipp b/include/boost/json/impl/serializer.ipp index d41578d6b..593eae042 100644 --- a/include/boost/json/impl/serializer.ipp +++ b/include/boost/json/impl/serializer.ipp @@ -729,31 +729,6 @@ write_value(stream& ss) } } -string_view -serializer:: -read_some( - char* dest, std::size_t size) -{ - // If this goes off it means you forgot - // to call reset() before seriailzing a - // new value, or you never checked done() - // to see if you should stop. - BOOST_ASSERT(! done_); - - stream ss(dest, size); - if(st_.empty()) - (this->*fn0_)(ss); - else - (this->*fn1_)(ss); - if(st_.empty()) - { - done_ = true; - p_ = nullptr; - } - return string_view( - dest, ss.used(dest)); -} - //---------------------------------------------------------- serializer:: @@ -829,7 +804,25 @@ read(char* dest, std::size_t size) static value const null; p_ = &null; } - return read_some(dest, size); + + // If this goes off it means you forgot + // to call reset() before seriailzing a + // new value, or you never checked done() + // to see if you should stop. + BOOST_ASSERT(! done_); + + stream ss(dest, size); + if(st_.empty()) + (this->*fn0_)(ss); + else + (this->*fn1_)(ss); + if(st_.empty()) + { + done_ = true; + p_ = nullptr; + } + return string_view( + dest, ss.used(dest)); } } // namespace json diff --git a/include/boost/json/serializer.hpp b/include/boost/json/serializer.hpp index 3b1eefa93..1c1e3fe33 100644 --- a/include/boost/json/serializer.hpp +++ b/include/boost/json/serializer.hpp @@ -89,7 +89,6 @@ class serializer template bool write_array (stream& ss); template bool write_object (stream& ss); template bool write_value (stream& ss); - inline string_view read_some(char* dest, std::size_t size); public: /// Move constructor (deleted) From f2ffc6759b57b38f91ee6a964f38377cff26c1bd Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Mon, 11 Dec 2023 12:59:13 +0300 Subject: [PATCH 09/10] pre-reset on empty source in serializer --- include/boost/json/impl/serializer.ipp | 5 +++-- include/boost/json/serializer.hpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/boost/json/impl/serializer.ipp b/include/boost/json/impl/serializer.ipp index 593eae042..8174cd454 100644 --- a/include/boost/json/impl/serializer.ipp +++ b/include/boost/json/impl/serializer.ipp @@ -799,10 +799,10 @@ string_view serializer:: read(char* dest, std::size_t size) { - if(! p_) + if(! fn0_) { static value const null; - p_ = &null; + reset(&null); } // If this goes off it means you forgot @@ -819,6 +819,7 @@ read(char* dest, std::size_t size) if(st_.empty()) { done_ = true; + fn0_ = nullptr; p_ = nullptr; } return string_view( diff --git a/include/boost/json/serializer.hpp b/include/boost/json/serializer.hpp index 1c1e3fe33..bb41bda69 100644 --- a/include/boost/json/serializer.hpp +++ b/include/boost/json/serializer.hpp @@ -68,8 +68,8 @@ class serializer using fn_t = bool (serializer::*)(stream&); void const* p_ = nullptr; - fn_t fn0_ = &serializer::write_null; - fn_t fn1_ = &serializer::write_null; + fn_t fn0_ = nullptr; + fn_t fn1_ = nullptr; detail::stack st_; const_stream cs0_; serialize_options opts_; From 796768f7c611d3957df776af388172eedf5767d2 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Mon, 11 Dec 2023 13:27:50 +0300 Subject: [PATCH 10/10] remove static null value --- include/boost/json/impl/serializer.ipp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/boost/json/impl/serializer.ipp b/include/boost/json/impl/serializer.ipp index 8174cd454..60bf2b9f3 100644 --- a/include/boost/json/impl/serializer.ipp +++ b/include/boost/json/impl/serializer.ipp @@ -801,7 +801,9 @@ read(char* dest, std::size_t size) { if(! fn0_) { - static value const null; + // the object is not going to be used if suspended, so we don't need + // it to outlive the call + value const null; reset(&null); }