diff --git a/include/boost/json/detail/impl/stack.hpp b/include/boost/json/detail/impl/stack.hpp new file mode 100644 index 000000000..aaf5ffc12 --- /dev/null +++ b/include/boost/json/detail/impl/stack.hpp @@ -0,0 +1,242 @@ +// +// 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 +#include +#include + +namespace boost { +namespace json { +namespace detail { + +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; +}; + +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 +stack:: +reserve_impl(std::size_t n) +{ + // caller checks this + BOOST_ASSERT(n > cap_); + + auto const base = static_cast( sp_->allocate(n) ); + if(base_) + { + // copy trivials + std::memcpy(base, base_, size_); + + // copy non-trivials + non_trivial<>* src = head_; + non_trivial<>* prev = nullptr; + non_trivial<>* head = nullptr; + while(src) + { + auto const next = src->next; + auto const offset = src->offset; + + std::size_t const buf_offset = + reinterpret_cast(src) - base_; + non_trivial<>* dest = 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_); + } + base_ = base; + cap_ = n; +} + +template +void +stack:: +push_unchecked(T const& t) +{ + constexpr std::size_t n = sizeof(T); + BOOST_STATIC_ASSERT( is_trivially_copy_assignable::value ); + BOOST_ASSERT( n <= cap_ - size_ ); + std::memcpy( base_ + size_, &t, n ); + size_ += n; +} + +template +void +stack:: +peek(T& t) +{ + constexpr std::size_t n = sizeof(T); + BOOST_STATIC_ASSERT( is_trivially_copy_assignable::value ); + BOOST_ASSERT( size_ >= n ); + std::memcpy( &t, base_ + size_ - n, n ); +} + +//-------------------------------------- + +// trivial +template +void +stack:: +push(T const& t, std::true_type) +{ + if( sizeof(T) > cap_ - size_ ) + reserve_impl( sizeof(T) + size_ ); + + push_unchecked(t); +} + +// non-trivial +template +void +stack:: +push(T&& t, std::false_type) +{ + BOOST_STATIC_ASSERT( ! is_trivially_copy_assignable::value ); + + 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; + do + { + std::size_t space = cap_ - size_; + unsigned char* buf = base_ + size_; + ptr = buf; + if( alignment::align(alignment, size, ptr, space) ) + { + offset = (reinterpret_cast(ptr) - buf) + size; + break; + } + + reserve_impl(size_ + size + alignment - 1); + } + while(true); + BOOST_ASSERT( + (reinterpret_cast(ptr) + size - offset) == + (base_ + size_) ); + + auto nt = ::new(ptr) Holder( static_cast(t) ); + nt->next = head_; + nt->offset = offset; + + head_ = nt; + size_ += offset; +} + +// trivial +template +void +stack:: +pop(T& t, std::true_type) +{ + BOOST_ASSERT( size_ >= sizeof(T) ); + peek(t); + size_ -= sizeof(T); +} + +// non-trivial +template +void +stack:: +pop(T& t, std::false_type) +{ + auto next = head_->next; + auto offset = head_->offset; + + 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; +} + +void +stack:: +clear() noexcept +{ + while(head_) + { + auto const next = head_->next; + head_->~non_trivial<>(); + head_ = next; + } + size_ = 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 + +#endif diff --git a/include/boost/json/detail/impl/stack.ipp b/include/boost/json/detail/impl/stack.ipp index ab0467821..3d122c627 100644 --- a/include/boost/json/detail/impl/stack.ipp +++ b/include/boost/json/detail/impl/stack.ipp @@ -17,43 +17,8 @@ namespace json { namespace detail { stack:: -~stack() -{ - 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:: -reserve(std::size_t n) -{ - if(cap_ >= n) - return; - auto const base = static_cast< - unsigned char*>(sp_->allocate(n)); - if(base_) - { - if(size_ > 0) - std::memcpy(base, base_, size_); - if(base_ != buf_) - sp_->deallocate(base_, cap_); - } - base_ = base; - cap_ = n; -} +non_trivial<>:: +~non_trivial() = default; } // detail } // namespace json diff --git a/include/boost/json/detail/stack.hpp b/include/boost/json/detail/stack.hpp index be2a6137b..89c451da7 100644 --- a/include/boost/json/detail/stack.hpp +++ b/include/boost/json/detail/stack.hpp @@ -12,26 +12,48 @@ #include #include +#include #include +#include 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 { + template< class T = void > + struct non_trivial; + storage_ptr sp_; std::size_t cap_ = 0; std::size_t size_ = 0; + non_trivial<>* head_ = nullptr; unsigned char* base_ = nullptr; unsigned char* buf_ = nullptr; public: - BOOST_JSON_DECL + inline ~stack(); stack() = default; + inline stack( storage_ptr sp, unsigned char* buf, @@ -43,67 +65,62 @@ class stack return size_ == 0; } + inline 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) + push(T&& 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; + using U = remove_cvref; + push( static_cast(t), is_trivially_copy_assignable() ); } 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); + using U = remove_cvref; + pop( t, is_trivially_copy_assignable() ); } + +private: + template void push( + T const& t, std::true_type); + template void push( + T&& t, std::false_type); + template void pop( + T& t, std::true_type); + template void pop( + T& t, std::false_type); + + inline + void + reserve_impl( + std::size_t n); }; } // detail } // namespace json } // namespace boost +#include + #endif diff --git a/include/boost/json/impl/serializer.ipp b/include/boost/json/impl/serializer.ipp index a54d9171b..60bf2b9f3 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)) @@ -724,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; - jv_ = nullptr; - } - return string_view( - dest, ss.used(dest)); -} - //---------------------------------------------------------- serializer:: @@ -764,11 +744,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 +755,7 @@ void serializer:: reset(array const* p) noexcept { - pa_ = p; + p_ = p; fn0_ = &serializer::write_array; fn1_ = &serializer::write_array; st_.clear(); @@ -788,7 +766,7 @@ void serializer:: reset(object const* p) noexcept { - po_ = p; + p_ = p; fn0_ = &serializer::write_object; fn1_ = &serializer::write_object; st_.clear(); @@ -821,12 +799,33 @@ string_view serializer:: read(char* dest, std::size_t size) { - if(! jv_) + if(! fn0_) + { + // 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); + } + + // 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()) { - static value const null; - jv_ = &null; + done_ = true; + fn0_ = nullptr; + p_ = nullptr; } - return read_some(dest, size); + return string_view( + dest, ss.used(dest)); } } // namespace json diff --git a/include/boost/json/serializer.hpp b/include/boost/json/serializer.hpp index 7f4524a79..bb41bda69 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 - fn_t fn0_ = &serializer::write_null; - fn_t fn1_ = &serializer::write_null; - value const* jv_ = nullptr; + void const* p_ = nullptr; + fn_t fn0_ = nullptr; + fn_t fn1_ = nullptr; detail::stack st_; const_stream cs0_; serialize_options opts_; @@ -97,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) diff --git a/test/serializer.cpp b/test/serializer.cpp index 034b75194..d30dff364 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,90 @@ 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.clear(); + 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); + + 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 ); + + 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() ); + } + BOOST_TEST( st.empty() ); + + st.push(1); + st.push(v); + st.clear(); + BOOST_TEST( st.empty() ); + } + void run() { @@ -606,6 +690,7 @@ class serializer_test testVectors(); testOstream(); testNumberRoundTrips(); + testStack(); } };