diff options
author | Alexander Rutkovsky <alexvru@mail.ru> | 2022-02-10 16:47:40 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:47:40 +0300 |
commit | 667a4ee7da2e004784b9c3cfab824a81e96f4d66 (patch) | |
tree | c0748b5dcbade83af788c0abfa89c0383d6b779c /library/cpp/actors/util/rope.h | |
parent | f3646f91e0de459836a7800b9ce3e8dc57a2ab3a (diff) | |
download | ydb-667a4ee7da2e004784b9c3cfab824a81e96f4d66.tar.gz |
Restoring authorship annotation for Alexander Rutkovsky <alexvru@mail.ru>. Commit 2 of 2.
Diffstat (limited to 'library/cpp/actors/util/rope.h')
-rw-r--r-- | library/cpp/actors/util/rope.h | 2000 |
1 files changed, 1000 insertions, 1000 deletions
diff --git a/library/cpp/actors/util/rope.h b/library/cpp/actors/util/rope.h index 9d3c3896c8..f5595efbaa 100644 --- a/library/cpp/actors/util/rope.h +++ b/library/cpp/actors/util/rope.h @@ -1,998 +1,998 @@ -#pragma once - -#include <util/generic/ptr.h> -#include <util/generic/string.h> -#include <util/generic/hash_set.h> -#include <util/stream/str.h> +#pragma once + +#include <util/generic/ptr.h> +#include <util/generic/string.h> +#include <util/generic/hash_set.h> +#include <util/stream/str.h> #include <util/system/sanitizers.h> #include <util/system/valgrind.h> - -// exactly one of them must be included -#include "rope_cont_list.h" -//#include "rope_cont_deque.h" - -struct IRopeChunkBackend : TThrRefBase { - using TData = std::tuple<const char*, size_t>; - virtual ~IRopeChunkBackend() = default; - virtual TData GetData() const = 0; - virtual size_t GetCapacity() const = 0; - using TPtr = TIntrusivePtr<IRopeChunkBackend>; -}; - -class TRopeAlignedBuffer : public IRopeChunkBackend { - static constexpr size_t Alignment = 16; - static constexpr size_t MallocAlignment = sizeof(size_t); - - ui32 Size; - const ui32 Capacity; - const ui32 Offset; - alignas(Alignment) char Data[]; - - TRopeAlignedBuffer(size_t size) - : Size(size) - , Capacity(size) - , Offset((Alignment - reinterpret_cast<uintptr_t>(Data)) & (Alignment - 1)) - { - Y_VERIFY(Offset <= Alignment - MallocAlignment); - } - -public: - static TIntrusivePtr<TRopeAlignedBuffer> Allocate(size_t size) { - return new(malloc(sizeof(TRopeAlignedBuffer) + size + Alignment - MallocAlignment)) TRopeAlignedBuffer(size); - } - - void *operator new(size_t) { - Y_FAIL(); - } - - void *operator new(size_t, void *ptr) { - return ptr; - } - - void operator delete(void *ptr) { - free(ptr); - } - + +// exactly one of them must be included +#include "rope_cont_list.h" +//#include "rope_cont_deque.h" + +struct IRopeChunkBackend : TThrRefBase { + using TData = std::tuple<const char*, size_t>; + virtual ~IRopeChunkBackend() = default; + virtual TData GetData() const = 0; + virtual size_t GetCapacity() const = 0; + using TPtr = TIntrusivePtr<IRopeChunkBackend>; +}; + +class TRopeAlignedBuffer : public IRopeChunkBackend { + static constexpr size_t Alignment = 16; + static constexpr size_t MallocAlignment = sizeof(size_t); + + ui32 Size; + const ui32 Capacity; + const ui32 Offset; + alignas(Alignment) char Data[]; + + TRopeAlignedBuffer(size_t size) + : Size(size) + , Capacity(size) + , Offset((Alignment - reinterpret_cast<uintptr_t>(Data)) & (Alignment - 1)) + { + Y_VERIFY(Offset <= Alignment - MallocAlignment); + } + +public: + static TIntrusivePtr<TRopeAlignedBuffer> Allocate(size_t size) { + return new(malloc(sizeof(TRopeAlignedBuffer) + size + Alignment - MallocAlignment)) TRopeAlignedBuffer(size); + } + + void *operator new(size_t) { + Y_FAIL(); + } + + void *operator new(size_t, void *ptr) { + return ptr; + } + + void operator delete(void *ptr) { + free(ptr); + } + void operator delete(void* p, void* ptr) { Y_UNUSED(p); Y_UNUSED(ptr); } - TData GetData() const override { - return {Data + Offset, Size}; - } - - size_t GetCapacity() const override { - return Capacity; - } - - char *GetBuffer() { - return Data + Offset; - } - - void AdjustSize(size_t size) { - Y_VERIFY(size <= Capacity); - Size = size; - } -}; - -namespace NRopeDetails { - - template<bool IsConst, typename TRope, typename TList> - struct TIteratorTraits; - - template<typename TRope, typename TList> - struct TIteratorTraits<true, TRope, TList> { - using TRopePtr = const TRope*; - using TListIterator = typename TList::const_iterator; - }; - - template<typename TRope, typename TList> - struct TIteratorTraits<false, TRope, TList> { - using TRopePtr = TRope*; - using TListIterator = typename TList::iterator; - }; - -} // NRopeDetails - -class TRopeArena; - -template<typename T> -struct always_false : std::false_type {}; - -class TRope { - friend class TRopeArena; - - struct TChunk - { - class TBackend { - enum class EType : uintptr_t { - STRING, - ROPE_CHUNK_BACKEND, - }; - - uintptr_t Owner = 0; // lower bits contain type of the owner - - public: - TBackend() = delete; - - TBackend(const TBackend& other) - : Owner(Clone(other.Owner)) - {} - - TBackend(TBackend&& other) - : Owner(std::exchange(other.Owner, 0)) - {} - - TBackend(TString s) - : Owner(Construct<TString>(EType::STRING, std::move(s))) - {} - - TBackend(IRopeChunkBackend::TPtr backend) - : Owner(Construct<IRopeChunkBackend::TPtr>(EType::ROPE_CHUNK_BACKEND, std::move(backend))) - {} - - ~TBackend() { - if (Owner) { - Destroy(Owner); - } - } - - TBackend& operator =(const TBackend& other) { - if (Owner) { - Destroy(Owner); - } - Owner = Clone(other.Owner); - return *this; - } - - TBackend& operator =(TBackend&& other) { - if (Owner) { - Destroy(Owner); - } - Owner = std::exchange(other.Owner, 0); - return *this; - } - - bool operator ==(const TBackend& other) const { - return Owner == other.Owner; - } - - const void *UniqueId() const { - return reinterpret_cast<const void*>(Owner); - } - - const IRopeChunkBackend::TData GetData() const { - return Visit(Owner, [](EType, auto& value) -> IRopeChunkBackend::TData { - using T = std::decay_t<decltype(value)>; - if constexpr (std::is_same_v<T, TString>) { - return {value.data(), value.size()}; - } else if constexpr (std::is_same_v<T, IRopeChunkBackend::TPtr>) { - return value->GetData(); - } else { - return {}; - } - }); - } - - size_t GetCapacity() const { - return Visit(Owner, [](EType, auto& value) { - using T = std::decay_t<decltype(value)>; - if constexpr (std::is_same_v<T, TString>) { - return value.capacity(); - } else if constexpr (std::is_same_v<T, IRopeChunkBackend::TPtr>) { - return value->GetCapacity(); - } else { - Y_FAIL(); - } - }); - } - - private: - static constexpr uintptr_t TypeMask = (1 << 3) - 1; - static constexpr uintptr_t ValueMask = ~TypeMask; - - template<typename T> - struct TObjectHolder { - struct TWrappedObject : TThrRefBase { - T Value; - TWrappedObject(T&& value) - : Value(std::move(value)) - {} - }; - TIntrusivePtr<TWrappedObject> Object; - - TObjectHolder(T&& object) - : Object(MakeIntrusive<TWrappedObject>(std::move(object))) - {} - }; - - template<typename TObject> - static uintptr_t Construct(EType type, TObject object) { - if constexpr (sizeof(TObject) <= sizeof(uintptr_t)) { - uintptr_t res = 0; - new(&res) TObject(std::move(object)); - Y_VERIFY_DEBUG((res & ValueMask) == res); - return res | static_cast<uintptr_t>(type); - } else { - return Construct<TObjectHolder<TObject>>(type, TObjectHolder<TObject>(std::move(object))); - } - } - - template<typename TCallback> - static std::invoke_result_t<TCallback, EType, TString&> VisitRaw(uintptr_t value, TCallback&& callback) { - Y_VERIFY_DEBUG(value); - const EType type = static_cast<EType>(value & TypeMask); - value &= ValueMask; - auto caller = [&](auto& value) { return std::invoke(std::forward<TCallback>(callback), type, value); }; - auto wrapper = [&](auto& value) { - using T = std::decay_t<decltype(value)>; - if constexpr (sizeof(T) <= sizeof(uintptr_t)) { - return caller(value); - } else { - return caller(reinterpret_cast<TObjectHolder<T>&>(value)); - } - }; - switch (type) { - case EType::STRING: return wrapper(reinterpret_cast<TString&>(value)); - case EType::ROPE_CHUNK_BACKEND: return wrapper(reinterpret_cast<IRopeChunkBackend::TPtr&>(value)); - } + TData GetData() const override { + return {Data + Offset, Size}; + } + + size_t GetCapacity() const override { + return Capacity; + } + + char *GetBuffer() { + return Data + Offset; + } + + void AdjustSize(size_t size) { + Y_VERIFY(size <= Capacity); + Size = size; + } +}; + +namespace NRopeDetails { + + template<bool IsConst, typename TRope, typename TList> + struct TIteratorTraits; + + template<typename TRope, typename TList> + struct TIteratorTraits<true, TRope, TList> { + using TRopePtr = const TRope*; + using TListIterator = typename TList::const_iterator; + }; + + template<typename TRope, typename TList> + struct TIteratorTraits<false, TRope, TList> { + using TRopePtr = TRope*; + using TListIterator = typename TList::iterator; + }; + +} // NRopeDetails + +class TRopeArena; + +template<typename T> +struct always_false : std::false_type {}; + +class TRope { + friend class TRopeArena; + + struct TChunk + { + class TBackend { + enum class EType : uintptr_t { + STRING, + ROPE_CHUNK_BACKEND, + }; + + uintptr_t Owner = 0; // lower bits contain type of the owner + + public: + TBackend() = delete; + + TBackend(const TBackend& other) + : Owner(Clone(other.Owner)) + {} + + TBackend(TBackend&& other) + : Owner(std::exchange(other.Owner, 0)) + {} + + TBackend(TString s) + : Owner(Construct<TString>(EType::STRING, std::move(s))) + {} + + TBackend(IRopeChunkBackend::TPtr backend) + : Owner(Construct<IRopeChunkBackend::TPtr>(EType::ROPE_CHUNK_BACKEND, std::move(backend))) + {} + + ~TBackend() { + if (Owner) { + Destroy(Owner); + } + } + + TBackend& operator =(const TBackend& other) { + if (Owner) { + Destroy(Owner); + } + Owner = Clone(other.Owner); + return *this; + } + + TBackend& operator =(TBackend&& other) { + if (Owner) { + Destroy(Owner); + } + Owner = std::exchange(other.Owner, 0); + return *this; + } + + bool operator ==(const TBackend& other) const { + return Owner == other.Owner; + } + + const void *UniqueId() const { + return reinterpret_cast<const void*>(Owner); + } + + const IRopeChunkBackend::TData GetData() const { + return Visit(Owner, [](EType, auto& value) -> IRopeChunkBackend::TData { + using T = std::decay_t<decltype(value)>; + if constexpr (std::is_same_v<T, TString>) { + return {value.data(), value.size()}; + } else if constexpr (std::is_same_v<T, IRopeChunkBackend::TPtr>) { + return value->GetData(); + } else { + return {}; + } + }); + } + + size_t GetCapacity() const { + return Visit(Owner, [](EType, auto& value) { + using T = std::decay_t<decltype(value)>; + if constexpr (std::is_same_v<T, TString>) { + return value.capacity(); + } else if constexpr (std::is_same_v<T, IRopeChunkBackend::TPtr>) { + return value->GetCapacity(); + } else { + Y_FAIL(); + } + }); + } + + private: + static constexpr uintptr_t TypeMask = (1 << 3) - 1; + static constexpr uintptr_t ValueMask = ~TypeMask; + + template<typename T> + struct TObjectHolder { + struct TWrappedObject : TThrRefBase { + T Value; + TWrappedObject(T&& value) + : Value(std::move(value)) + {} + }; + TIntrusivePtr<TWrappedObject> Object; + + TObjectHolder(T&& object) + : Object(MakeIntrusive<TWrappedObject>(std::move(object))) + {} + }; + + template<typename TObject> + static uintptr_t Construct(EType type, TObject object) { + if constexpr (sizeof(TObject) <= sizeof(uintptr_t)) { + uintptr_t res = 0; + new(&res) TObject(std::move(object)); + Y_VERIFY_DEBUG((res & ValueMask) == res); + return res | static_cast<uintptr_t>(type); + } else { + return Construct<TObjectHolder<TObject>>(type, TObjectHolder<TObject>(std::move(object))); + } + } + + template<typename TCallback> + static std::invoke_result_t<TCallback, EType, TString&> VisitRaw(uintptr_t value, TCallback&& callback) { + Y_VERIFY_DEBUG(value); + const EType type = static_cast<EType>(value & TypeMask); + value &= ValueMask; + auto caller = [&](auto& value) { return std::invoke(std::forward<TCallback>(callback), type, value); }; + auto wrapper = [&](auto& value) { + using T = std::decay_t<decltype(value)>; + if constexpr (sizeof(T) <= sizeof(uintptr_t)) { + return caller(value); + } else { + return caller(reinterpret_cast<TObjectHolder<T>&>(value)); + } + }; + switch (type) { + case EType::STRING: return wrapper(reinterpret_cast<TString&>(value)); + case EType::ROPE_CHUNK_BACKEND: return wrapper(reinterpret_cast<IRopeChunkBackend::TPtr&>(value)); + } Y_FAIL("Unexpected type# %" PRIu64, static_cast<ui64>(type)); - } - - template<typename TCallback> - static std::invoke_result_t<TCallback, EType, TString&> Visit(uintptr_t value, TCallback&& callback) { - return VisitRaw(value, [&](EType type, auto& value) { - return std::invoke(std::forward<TCallback>(callback), type, Unwrap(value)); - }); - } - - template<typename T> static T& Unwrap(T& object) { return object; } - template<typename T> static T& Unwrap(TObjectHolder<T>& holder) { return holder.Object->Value; } - - static uintptr_t Clone(uintptr_t value) { - return VisitRaw(value, [](EType type, auto& value) { return Construct(type, value); }); - } - - static void Destroy(uintptr_t value) { - VisitRaw(value, [](EType, auto& value) { CallDtor(value); }); - } - - template<typename T> - static void CallDtor(T& value) { - value.~T(); - } - }; - - TBackend Backend; // who actually holds the data - const char *Begin; // data start - const char *End; // data end - - static constexpr struct TSlice {} Slice{}; - - template<typename T> - TChunk(T&& backend, const IRopeChunkBackend::TData& data) - : Backend(std::move(backend)) - , Begin(std::get<0>(data)) - , End(Begin + std::get<1>(data)) - { - Y_VERIFY_DEBUG(Begin != End); - } - - TChunk(TString s) - : Backend(std::move(s)) - { - size_t size; - std::tie(Begin, size) = Backend.GetData(); - End = Begin + size; - } - - TChunk(IRopeChunkBackend::TPtr backend) - : TChunk(backend, backend->GetData()) - {} - - TChunk(TSlice, const char *data, size_t size, const TChunk& from) - : TChunk(from.Backend, {data, size}) - {} - - TChunk(TSlice, const char *begin, const char *end, const TChunk& from) - : TChunk(Slice, begin, end - begin, from) - {} - - explicit TChunk(const TChunk& other) - : Backend(other.Backend) - , Begin(other.Begin) - , End(other.End) - {} - - TChunk(TChunk&& other) - : Backend(std::move(other.Backend)) - , Begin(other.Begin) - , End(other.End) - {} - - TChunk& operator =(const TChunk&) = default; - TChunk& operator =(TChunk&&) = default; - - size_t GetSize() const { - return End - Begin; - } - - static void Clear(TChunk& chunk) { - chunk.Begin = nullptr; - } - - static bool IsInUse(const TChunk& chunk) { - return chunk.Begin != nullptr; - } - - size_t GetCapacity() const { - return Backend.GetCapacity(); - } - }; - - using TChunkList = NRopeDetails::TChunkList<TChunk>; - -private: - // we use list here to store chain items as we have to keep valid iterators when erase/insert operations are invoked; - // iterator uses underlying container's iterator, so we have to use container that keeps valid iterators on delete, - // thus, the list - TChunkList Chain; - size_t Size = 0; - -private: - template<bool IsConst> - class TIteratorImpl { - using TTraits = NRopeDetails::TIteratorTraits<IsConst, TRope, TChunkList>; - - typename TTraits::TRopePtr Rope; - typename TTraits::TListIterator Iter; - const char *Ptr; // ptr is always nullptr when iterator is positioned at the rope end - -#ifndef NDEBUG - ui32 ValidityToken; -#endif - - private: - TIteratorImpl(typename TTraits::TRopePtr rope, typename TTraits::TListIterator iter, const char *ptr = nullptr) - : Rope(rope) - , Iter(iter) - , Ptr(ptr) -#ifndef NDEBUG - , ValidityToken(Rope->GetValidityToken()) -#endif - {} - - public: - TIteratorImpl() - : Rope(nullptr) - , Ptr(nullptr) - {} - - template<bool IsOtherConst> - TIteratorImpl(const TIteratorImpl<IsOtherConst>& other) - : Rope(other.Rope) - , Iter(other.Iter) - , Ptr(other.Ptr) -#ifndef NDEBUG - , ValidityToken(other.ValidityToken) -#endif - {} - - void CheckValid() const { -#ifndef NDEBUG - Y_VERIFY(ValidityToken == Rope->GetValidityToken()); -#endif - } - - TIteratorImpl& operator +=(size_t amount) { - CheckValid(); - - while (amount) { - Y_VERIFY_DEBUG(Valid()); - const size_t max = ContiguousSize(); - const size_t num = std::min(amount, max); - amount -= num; - Ptr += num; - if (Ptr == Iter->End) { - AdvanceToNextContiguousBlock(); - } - } - - return *this; - } - - TIteratorImpl operator +(size_t amount) const { - CheckValid(); - - return TIteratorImpl(*this) += amount; - } - - TIteratorImpl& operator -=(size_t amount) { - CheckValid(); - - while (amount) { - const size_t num = Ptr ? std::min<size_t>(amount, Ptr - Iter->Begin) : 0; - amount -= num; - Ptr -= num; - if (amount) { - Y_VERIFY_DEBUG(Iter != GetChainBegin()); - --Iter; - Ptr = Iter->End; - } - } - - return *this; - } - - TIteratorImpl operator -(size_t amount) const { - CheckValid(); - return TIteratorImpl(*this) -= amount; - } - - std::pair<const char*, size_t> operator *() const { - return {ContiguousData(), ContiguousSize()}; - } - - TIteratorImpl& operator ++() { - AdvanceToNextContiguousBlock(); - return *this; - } - - TIteratorImpl operator ++(int) const { - auto it(*this); - it.AdvanceToNextContiguousBlock(); - return it; - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Operation with contiguous data - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Get the pointer to the contiguous block of data; valid locations are [Data; Data + Size). - const char *ContiguousData() const { - CheckValid(); - return Ptr; - } - - // Get the amount of contiguous block. - size_t ContiguousSize() const { - CheckValid(); - return Ptr ? Iter->End - Ptr : 0; - } - + } + + template<typename TCallback> + static std::invoke_result_t<TCallback, EType, TString&> Visit(uintptr_t value, TCallback&& callback) { + return VisitRaw(value, [&](EType type, auto& value) { + return std::invoke(std::forward<TCallback>(callback), type, Unwrap(value)); + }); + } + + template<typename T> static T& Unwrap(T& object) { return object; } + template<typename T> static T& Unwrap(TObjectHolder<T>& holder) { return holder.Object->Value; } + + static uintptr_t Clone(uintptr_t value) { + return VisitRaw(value, [](EType type, auto& value) { return Construct(type, value); }); + } + + static void Destroy(uintptr_t value) { + VisitRaw(value, [](EType, auto& value) { CallDtor(value); }); + } + + template<typename T> + static void CallDtor(T& value) { + value.~T(); + } + }; + + TBackend Backend; // who actually holds the data + const char *Begin; // data start + const char *End; // data end + + static constexpr struct TSlice {} Slice{}; + + template<typename T> + TChunk(T&& backend, const IRopeChunkBackend::TData& data) + : Backend(std::move(backend)) + , Begin(std::get<0>(data)) + , End(Begin + std::get<1>(data)) + { + Y_VERIFY_DEBUG(Begin != End); + } + + TChunk(TString s) + : Backend(std::move(s)) + { + size_t size; + std::tie(Begin, size) = Backend.GetData(); + End = Begin + size; + } + + TChunk(IRopeChunkBackend::TPtr backend) + : TChunk(backend, backend->GetData()) + {} + + TChunk(TSlice, const char *data, size_t size, const TChunk& from) + : TChunk(from.Backend, {data, size}) + {} + + TChunk(TSlice, const char *begin, const char *end, const TChunk& from) + : TChunk(Slice, begin, end - begin, from) + {} + + explicit TChunk(const TChunk& other) + : Backend(other.Backend) + , Begin(other.Begin) + , End(other.End) + {} + + TChunk(TChunk&& other) + : Backend(std::move(other.Backend)) + , Begin(other.Begin) + , End(other.End) + {} + + TChunk& operator =(const TChunk&) = default; + TChunk& operator =(TChunk&&) = default; + + size_t GetSize() const { + return End - Begin; + } + + static void Clear(TChunk& chunk) { + chunk.Begin = nullptr; + } + + static bool IsInUse(const TChunk& chunk) { + return chunk.Begin != nullptr; + } + + size_t GetCapacity() const { + return Backend.GetCapacity(); + } + }; + + using TChunkList = NRopeDetails::TChunkList<TChunk>; + +private: + // we use list here to store chain items as we have to keep valid iterators when erase/insert operations are invoked; + // iterator uses underlying container's iterator, so we have to use container that keeps valid iterators on delete, + // thus, the list + TChunkList Chain; + size_t Size = 0; + +private: + template<bool IsConst> + class TIteratorImpl { + using TTraits = NRopeDetails::TIteratorTraits<IsConst, TRope, TChunkList>; + + typename TTraits::TRopePtr Rope; + typename TTraits::TListIterator Iter; + const char *Ptr; // ptr is always nullptr when iterator is positioned at the rope end + +#ifndef NDEBUG + ui32 ValidityToken; +#endif + + private: + TIteratorImpl(typename TTraits::TRopePtr rope, typename TTraits::TListIterator iter, const char *ptr = nullptr) + : Rope(rope) + , Iter(iter) + , Ptr(ptr) +#ifndef NDEBUG + , ValidityToken(Rope->GetValidityToken()) +#endif + {} + + public: + TIteratorImpl() + : Rope(nullptr) + , Ptr(nullptr) + {} + + template<bool IsOtherConst> + TIteratorImpl(const TIteratorImpl<IsOtherConst>& other) + : Rope(other.Rope) + , Iter(other.Iter) + , Ptr(other.Ptr) +#ifndef NDEBUG + , ValidityToken(other.ValidityToken) +#endif + {} + + void CheckValid() const { +#ifndef NDEBUG + Y_VERIFY(ValidityToken == Rope->GetValidityToken()); +#endif + } + + TIteratorImpl& operator +=(size_t amount) { + CheckValid(); + + while (amount) { + Y_VERIFY_DEBUG(Valid()); + const size_t max = ContiguousSize(); + const size_t num = std::min(amount, max); + amount -= num; + Ptr += num; + if (Ptr == Iter->End) { + AdvanceToNextContiguousBlock(); + } + } + + return *this; + } + + TIteratorImpl operator +(size_t amount) const { + CheckValid(); + + return TIteratorImpl(*this) += amount; + } + + TIteratorImpl& operator -=(size_t amount) { + CheckValid(); + + while (amount) { + const size_t num = Ptr ? std::min<size_t>(amount, Ptr - Iter->Begin) : 0; + amount -= num; + Ptr -= num; + if (amount) { + Y_VERIFY_DEBUG(Iter != GetChainBegin()); + --Iter; + Ptr = Iter->End; + } + } + + return *this; + } + + TIteratorImpl operator -(size_t amount) const { + CheckValid(); + return TIteratorImpl(*this) -= amount; + } + + std::pair<const char*, size_t> operator *() const { + return {ContiguousData(), ContiguousSize()}; + } + + TIteratorImpl& operator ++() { + AdvanceToNextContiguousBlock(); + return *this; + } + + TIteratorImpl operator ++(int) const { + auto it(*this); + it.AdvanceToNextContiguousBlock(); + return it; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Operation with contiguous data + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Get the pointer to the contiguous block of data; valid locations are [Data; Data + Size). + const char *ContiguousData() const { + CheckValid(); + return Ptr; + } + + // Get the amount of contiguous block. + size_t ContiguousSize() const { + CheckValid(); + return Ptr ? Iter->End - Ptr : 0; + } + size_t ChunkOffset() const { return Ptr ? Ptr - Iter->Begin : 0; } - // Advance to next contiguous block of data. - void AdvanceToNextContiguousBlock() { - CheckValid(); - Y_VERIFY_DEBUG(Valid()); - ++Iter; - Ptr = Iter != GetChainEnd() ? Iter->Begin : nullptr; - } - - // Extract some data and advance. Size is not checked here, to it must be provided valid. - void ExtractPlainDataAndAdvance(void *buffer, size_t len) { - CheckValid(); - - while (len) { - Y_VERIFY_DEBUG(Ptr); - - // calculate amount of bytes we need to move - const size_t max = ContiguousSize(); - const size_t num = std::min(len, max); - - // copy data to the buffer and advance buffer pointers - memcpy(buffer, Ptr, num); - buffer = static_cast<char*>(buffer) + num; - len -= num; - - // advance iterator itself - Ptr += num; - if (Ptr == Iter->End) { - AdvanceToNextContiguousBlock(); - } - } - } - - // Checks if the iterator points to the end of the rope or not. - bool Valid() const { - CheckValid(); - return Ptr; - } - - template<bool IsOtherConst> - bool operator ==(const TIteratorImpl<IsOtherConst>& other) const { - Y_VERIFY_DEBUG(Rope == other.Rope); - CheckValid(); - other.CheckValid(); - return Iter == other.Iter && Ptr == other.Ptr; - } - - template<bool IsOtherConst> - bool operator !=(const TIteratorImpl<IsOtherConst>& other) const { - CheckValid(); - other.CheckValid(); - return !(*this == other); - } - - private: - friend class TRope; - - typename TTraits::TListIterator operator ->() const { - CheckValid(); - return Iter; - } - - const TChunk& GetChunk() const { - CheckValid(); - return *Iter; - } - - typename TTraits::TListIterator GetChainBegin() const { - CheckValid(); - return Rope->Chain.begin(); - } - - typename TTraits::TListIterator GetChainEnd() const { - CheckValid(); - return Rope->Chain.end(); - } - - bool PointsToChunkMiddle() const { - CheckValid(); - return Ptr && Ptr != Iter->Begin; - } - }; - -public: -#ifndef NDEBUG - ui32 ValidityToken = 0; - ui32 GetValidityToken() const { return ValidityToken; } - void InvalidateIterators() { ++ValidityToken; } -#else - void InvalidateIterators() {} -#endif - -public: - using TConstIterator = TIteratorImpl<true>; - using TIterator = TIteratorImpl<false>; - -public: - TRope() = default; - TRope(const TRope& rope) = default; - - TRope(TRope&& rope) - : Chain(std::move(rope.Chain)) - , Size(std::exchange(rope.Size, 0)) - { - rope.InvalidateIterators(); - } - - TRope(TString s) { - if (s) { - Size = s.size(); + // Advance to next contiguous block of data. + void AdvanceToNextContiguousBlock() { + CheckValid(); + Y_VERIFY_DEBUG(Valid()); + ++Iter; + Ptr = Iter != GetChainEnd() ? Iter->Begin : nullptr; + } + + // Extract some data and advance. Size is not checked here, to it must be provided valid. + void ExtractPlainDataAndAdvance(void *buffer, size_t len) { + CheckValid(); + + while (len) { + Y_VERIFY_DEBUG(Ptr); + + // calculate amount of bytes we need to move + const size_t max = ContiguousSize(); + const size_t num = std::min(len, max); + + // copy data to the buffer and advance buffer pointers + memcpy(buffer, Ptr, num); + buffer = static_cast<char*>(buffer) + num; + len -= num; + + // advance iterator itself + Ptr += num; + if (Ptr == Iter->End) { + AdvanceToNextContiguousBlock(); + } + } + } + + // Checks if the iterator points to the end of the rope or not. + bool Valid() const { + CheckValid(); + return Ptr; + } + + template<bool IsOtherConst> + bool operator ==(const TIteratorImpl<IsOtherConst>& other) const { + Y_VERIFY_DEBUG(Rope == other.Rope); + CheckValid(); + other.CheckValid(); + return Iter == other.Iter && Ptr == other.Ptr; + } + + template<bool IsOtherConst> + bool operator !=(const TIteratorImpl<IsOtherConst>& other) const { + CheckValid(); + other.CheckValid(); + return !(*this == other); + } + + private: + friend class TRope; + + typename TTraits::TListIterator operator ->() const { + CheckValid(); + return Iter; + } + + const TChunk& GetChunk() const { + CheckValid(); + return *Iter; + } + + typename TTraits::TListIterator GetChainBegin() const { + CheckValid(); + return Rope->Chain.begin(); + } + + typename TTraits::TListIterator GetChainEnd() const { + CheckValid(); + return Rope->Chain.end(); + } + + bool PointsToChunkMiddle() const { + CheckValid(); + return Ptr && Ptr != Iter->Begin; + } + }; + +public: +#ifndef NDEBUG + ui32 ValidityToken = 0; + ui32 GetValidityToken() const { return ValidityToken; } + void InvalidateIterators() { ++ValidityToken; } +#else + void InvalidateIterators() {} +#endif + +public: + using TConstIterator = TIteratorImpl<true>; + using TIterator = TIteratorImpl<false>; + +public: + TRope() = default; + TRope(const TRope& rope) = default; + + TRope(TRope&& rope) + : Chain(std::move(rope.Chain)) + , Size(std::exchange(rope.Size, 0)) + { + rope.InvalidateIterators(); + } + + TRope(TString s) { + if (s) { + Size = s.size(); s.reserve(32); - Chain.PutToEnd(std::move(s)); - } - } - - TRope(IRopeChunkBackend::TPtr item) { - std::tie(std::ignore, Size) = item->GetData(); - Chain.PutToEnd(std::move(item)); - } - - TRope(TConstIterator begin, TConstIterator end) { - Y_VERIFY_DEBUG(begin.Rope == end.Rope); - if (begin.Rope == this) { - TRope temp(begin, end); - *this = std::move(temp); - return; - } - - while (begin.Iter != end.Iter) { - const size_t size = begin.ContiguousSize(); - Chain.PutToEnd(TChunk::Slice, begin.ContiguousData(), size, begin.GetChunk()); - begin.AdvanceToNextContiguousBlock(); - Size += size; - } - - if (begin != end && end.PointsToChunkMiddle()) { - Chain.PutToEnd(TChunk::Slice, begin.Ptr, end.Ptr, begin.GetChunk()); - Size += end.Ptr - begin.Ptr; - } - } - - ~TRope() { - } - - // creates a copy of rope with chunks with inefficient storage ratio being copied with arena allocator - static TRope CopySpaceOptimized(TRope&& origin, size_t worstRatioPer1k, TRopeArena& arena); - - TRope& operator=(const TRope& other) { - Chain = other.Chain; - Size = other.Size; - return *this; - } - - TRope& operator=(TRope&& other) { - Chain = std::move(other.Chain); - Size = std::exchange(other.Size, 0); - InvalidateIterators(); - other.InvalidateIterators(); - return *this; - } - - size_t GetSize() const { - return Size; - } - - bool IsEmpty() const { - return !Size; - } - - operator bool() const { - return Chain; - } - - TIterator Begin() { - return *this ? TIterator(this, Chain.begin(), Chain.GetFirstChunk().Begin) : End(); - } - - TIterator End() { - return TIterator(this, Chain.end()); - } - - TIterator Iterator(TChunkList::iterator it) { - return TIterator(this, it, it != Chain.end() ? it->Begin : nullptr); - } - - TIterator Position(size_t index) { - return Begin() + index; - } - - TConstIterator Begin() const { - return *this ? TConstIterator(this, Chain.begin(), Chain.GetFirstChunk().Begin) : End(); - } - - TConstIterator End() const { - return TConstIterator(this, Chain.end()); - } - - TConstIterator Position(size_t index) const { - return Begin() + index; - } - - TConstIterator begin() const { return Begin(); } - TConstIterator end() const { return End(); } - - void Erase(TIterator begin, TIterator end) { - Cut(begin, end, nullptr); - } - - TRope Extract(TIterator begin, TIterator end) { - TRope res; - Cut(begin, end, &res); - return res; - } - - void ExtractFront(size_t num, TRope *dest) { - Y_VERIFY(Size >= num); - if (num == Size && !*dest) { - *dest = std::move(*this); - return; - } - Size -= num; - dest->Size += num; - TChunkList::iterator it, first = Chain.begin(); - for (it = first; num && num >= it->GetSize(); ++it) { - num -= it->GetSize(); - } - if (it != first) { - if (dest->Chain) { - auto& last = dest->Chain.GetLastChunk(); - if (last.Backend == first->Backend && last.End == first->Begin) { - last.End = first->End; - first = Chain.Erase(first); // TODO(alexvru): "it" gets invalidated here on some containers - } - } - dest->Chain.Splice(dest->Chain.end(), Chain, first, it); - } - if (num) { - auto it = Chain.begin(); - if (dest->Chain) { - auto& last = dest->Chain.GetLastChunk(); - if (last.Backend == first->Backend && last.End == first->Begin) { - first->Begin += num; - last.End = first->Begin; - return; - } - } - dest->Chain.PutToEnd(TChunk::Slice, it->Begin, it->Begin + num, *it); - it->Begin += num; - } - } - - void Insert(TIterator pos, TRope&& rope) { - Y_VERIFY_DEBUG(this == pos.Rope); - Y_VERIFY_DEBUG(this != &rope); - - if (!rope) { - return; // do nothing for empty rope - } - - // adjust size - Size += std::exchange(rope.Size, 0); - - // check if we have to split the block - if (pos.PointsToChunkMiddle()) { - pos.Iter = Chain.InsertBefore(pos.Iter, TChunk::Slice, pos->Begin, pos.Ptr, pos.GetChunk()); - ++pos.Iter; - pos->Begin = pos.Ptr; - } - - // perform glueing if possible - TChunk *ropeLeft = &rope.Chain.GetFirstChunk(); - TChunk *ropeRight = &rope.Chain.GetLastChunk(); - bool gluedLeft = false, gluedRight = false; - if (pos.Iter != Chain.begin()) { // glue left part whenever possible - // obtain iterator to previous chunk - auto prev(pos.Iter); - --prev; - if (prev->End == ropeLeft->Begin && prev->Backend == ropeLeft->Backend) { // it is glueable - prev->End = ropeLeft->End; - gluedLeft = true; - } - } - if (pos.Iter != Chain.end() && ropeRight->End == pos->Begin && ropeRight->Backend == pos->Backend) { - pos->Begin = ropeRight->Begin; - gluedRight = true; - } - if (gluedLeft) { - rope.Chain.EraseFront(); - } - if (gluedRight) { - if (rope) { - rope.Chain.EraseBack(); - } else { // it looks like double-glueing for the same chunk, we have to drop previous one - auto prev(pos.Iter); - --prev; - pos->Begin = prev->Begin; - pos.Iter = Chain.Erase(prev); - } - } - if (rope) { // insert remains - Chain.Splice(pos.Iter, rope.Chain, rope.Chain.begin(), rope.Chain.end()); - } - Y_VERIFY_DEBUG(!rope); - InvalidateIterators(); - } - - void EraseFront(size_t len) { - Y_VERIFY_DEBUG(Size >= len); - Size -= len; - - while (len) { - Y_VERIFY_DEBUG(Chain); - TChunk& item = Chain.GetFirstChunk(); - const size_t itemSize = item.GetSize(); - if (len >= itemSize) { - Chain.EraseFront(); - len -= itemSize; - } else { - item.Begin += len; - break; - } - } - - InvalidateIterators(); - } - - void EraseBack(size_t len) { - Y_VERIFY_DEBUG(Size >= len); - Size -= len; - - while (len) { - Y_VERIFY_DEBUG(Chain); - TChunk& item = Chain.GetLastChunk(); - const size_t itemSize = item.GetSize(); - if (len >= itemSize) { - Chain.EraseBack(); - len -= itemSize; - } else { - item.End -= len; - break; - } - } - - InvalidateIterators(); - } - - bool ExtractFrontPlain(void *buffer, size_t len) { - // check if we have enough data in the rope - if (Size < len) { - return false; - } - Size -= len; - while (len) { - auto& chunk = Chain.GetFirstChunk(); - const size_t num = Min(len, chunk.GetSize()); - memcpy(buffer, chunk.Begin, num); - buffer = static_cast<char*>(buffer) + num; - len -= num; - chunk.Begin += num; - if (chunk.Begin == chunk.End) { - Chain.Erase(Chain.begin()); - } - } - InvalidateIterators(); - return true; - } - - bool FetchFrontPlain(char **ptr, size_t *remain) { - const size_t num = Min(*remain, Size); - ExtractFrontPlain(*ptr, num); - *ptr += num; - *remain -= num; - return !*remain; - } - - static int Compare(const TRope& x, const TRope& y) { - TConstIterator xIter = x.Begin(), yIter = y.Begin(); - while (xIter.Valid() && yIter.Valid()) { - const size_t step = std::min(xIter.ContiguousSize(), yIter.ContiguousSize()); - if (int res = memcmp(xIter.ContiguousData(), yIter.ContiguousData(), step)) { - return res; - } - xIter += step; - yIter += step; - } - return xIter.Valid() - yIter.Valid(); - } - - // Use this method carefully -- it may significantly reduce performance when misused. - TString ConvertToString() const { - TString res = TString::Uninitialized(GetSize()); - Begin().ExtractPlainDataAndAdvance(res.Detach(), res.size()); - return res; - } - - TString DebugString() const { - TStringStream s; - s << "{Size# " << Size; - for (const auto& chunk : Chain) { - const char *data; - std::tie(data, std::ignore) = chunk.Backend.GetData(); - s << " [" << chunk.Begin - data << ", " << chunk.End - data << ")@" << chunk.Backend.UniqueId(); - } - s << "}"; - return s.Str(); - } - - friend bool operator==(const TRope& x, const TRope& y) { return Compare(x, y) == 0; } - friend bool operator!=(const TRope& x, const TRope& y) { return Compare(x, y) != 0; } - friend bool operator< (const TRope& x, const TRope& y) { return Compare(x, y) < 0; } - friend bool operator<=(const TRope& x, const TRope& y) { return Compare(x, y) <= 0; } - friend bool operator> (const TRope& x, const TRope& y) { return Compare(x, y) > 0; } - friend bool operator>=(const TRope& x, const TRope& y) { return Compare(x, y) >= 0; } - -private: - void Cut(TIterator begin, TIterator end, TRope *target) { - // ensure all iterators are belong to us - Y_VERIFY_DEBUG(this == begin.Rope && this == end.Rope); - - // if begin and end are equal, we do nothing -- checking this case allows us to find out that begin does not - // point to End(), for example - if (begin == end) { - return; - } - - auto addBlock = [&](const TChunk& from, const char *begin, const char *end) { - if (target) { - target->Chain.PutToEnd(TChunk::Slice, begin, end, from); - target->Size += end - begin; - } - Size -= end - begin; - }; - - // consider special case -- when begin and end point to the same block; in this case we have to split up this - // block into two parts - if (begin.Iter == end.Iter) { - addBlock(begin.GetChunk(), begin.Ptr, end.Ptr); - const char *firstChunkBegin = begin.PointsToChunkMiddle() ? begin->Begin : nullptr; - begin->Begin = end.Ptr; // this affects both begin and end iterator pointed values - if (firstChunkBegin) { - Chain.InsertBefore(begin.Iter, TChunk::Slice, firstChunkBegin, begin.Ptr, begin.GetChunk()); - } - } else { - // check the first iterator -- if it starts not from the begin of the block, we have to adjust end of the - // first block to match begin iterator and switch to next block - if (begin.PointsToChunkMiddle()) { - addBlock(begin.GetChunk(), begin.Ptr, begin->End); - begin->End = begin.Ptr; - begin.AdvanceToNextContiguousBlock(); - } - - // now drop full blocks - size_t rangeSize = 0; - for (auto it = begin.Iter; it != end.Iter; ++it) { - Y_VERIFY_DEBUG(it->GetSize()); - rangeSize += it->GetSize(); - } - if (rangeSize) { - if (target) { - end.Iter = target->Chain.Splice(target->Chain.end(), Chain, begin.Iter, end.Iter); - target->Size += rangeSize; - } else { - end.Iter = Chain.Erase(begin.Iter, end.Iter); - } - Size -= rangeSize; - } - - // and cut the last block if necessary - if (end.PointsToChunkMiddle()) { - addBlock(end.GetChunk(), end->Begin, end.Ptr); - end->Begin = end.Ptr; - } - } - - InvalidateIterators(); - } -}; - -class TRopeArena { - using TAllocateCallback = std::function<TIntrusivePtr<IRopeChunkBackend>()>; - - TAllocateCallback Allocator; - TRope Arena; - size_t Size = 0; - THashSet<const void*> AccountedBuffers; - -public: - TRopeArena(TAllocateCallback&& allocator) - : Allocator(std::move(allocator)) - {} - - TRope CreateRope(const void *buffer, size_t len) { - TRope res; - - while (len) { - if (Arena) { - auto iter = Arena.Begin(); - Y_VERIFY_DEBUG(iter.Valid()); - char *dest = const_cast<char*>(iter.ContiguousData()); - const size_t bytesToCopy = std::min(len, iter.ContiguousSize()); - memcpy(dest, buffer, bytesToCopy); - buffer = static_cast<const char*>(buffer) + bytesToCopy; - len -= bytesToCopy; - res.Insert(res.End(), Arena.Extract(Arena.Begin(), Arena.Position(bytesToCopy))); - } else { - Arena.Insert(Arena.End(), TRope(Allocator())); - } - } - - // align arena on 8-byte boundary - const size_t align = 8; - if (const size_t padding = Arena.GetSize() % align) { - Arena.EraseFront(padding); - } - - return res; - } - - size_t GetSize() const { - return Size; - } - - void AccountChunk(const TRope::TChunk& chunk) { - if (AccountedBuffers.insert(chunk.Backend.UniqueId()).second) { - Size += chunk.GetCapacity(); - } - } -}; - + Chain.PutToEnd(std::move(s)); + } + } + + TRope(IRopeChunkBackend::TPtr item) { + std::tie(std::ignore, Size) = item->GetData(); + Chain.PutToEnd(std::move(item)); + } + + TRope(TConstIterator begin, TConstIterator end) { + Y_VERIFY_DEBUG(begin.Rope == end.Rope); + if (begin.Rope == this) { + TRope temp(begin, end); + *this = std::move(temp); + return; + } + + while (begin.Iter != end.Iter) { + const size_t size = begin.ContiguousSize(); + Chain.PutToEnd(TChunk::Slice, begin.ContiguousData(), size, begin.GetChunk()); + begin.AdvanceToNextContiguousBlock(); + Size += size; + } + + if (begin != end && end.PointsToChunkMiddle()) { + Chain.PutToEnd(TChunk::Slice, begin.Ptr, end.Ptr, begin.GetChunk()); + Size += end.Ptr - begin.Ptr; + } + } + + ~TRope() { + } + + // creates a copy of rope with chunks with inefficient storage ratio being copied with arena allocator + static TRope CopySpaceOptimized(TRope&& origin, size_t worstRatioPer1k, TRopeArena& arena); + + TRope& operator=(const TRope& other) { + Chain = other.Chain; + Size = other.Size; + return *this; + } + + TRope& operator=(TRope&& other) { + Chain = std::move(other.Chain); + Size = std::exchange(other.Size, 0); + InvalidateIterators(); + other.InvalidateIterators(); + return *this; + } + + size_t GetSize() const { + return Size; + } + + bool IsEmpty() const { + return !Size; + } + + operator bool() const { + return Chain; + } + + TIterator Begin() { + return *this ? TIterator(this, Chain.begin(), Chain.GetFirstChunk().Begin) : End(); + } + + TIterator End() { + return TIterator(this, Chain.end()); + } + + TIterator Iterator(TChunkList::iterator it) { + return TIterator(this, it, it != Chain.end() ? it->Begin : nullptr); + } + + TIterator Position(size_t index) { + return Begin() + index; + } + + TConstIterator Begin() const { + return *this ? TConstIterator(this, Chain.begin(), Chain.GetFirstChunk().Begin) : End(); + } + + TConstIterator End() const { + return TConstIterator(this, Chain.end()); + } + + TConstIterator Position(size_t index) const { + return Begin() + index; + } + + TConstIterator begin() const { return Begin(); } + TConstIterator end() const { return End(); } + + void Erase(TIterator begin, TIterator end) { + Cut(begin, end, nullptr); + } + + TRope Extract(TIterator begin, TIterator end) { + TRope res; + Cut(begin, end, &res); + return res; + } + + void ExtractFront(size_t num, TRope *dest) { + Y_VERIFY(Size >= num); + if (num == Size && !*dest) { + *dest = std::move(*this); + return; + } + Size -= num; + dest->Size += num; + TChunkList::iterator it, first = Chain.begin(); + for (it = first; num && num >= it->GetSize(); ++it) { + num -= it->GetSize(); + } + if (it != first) { + if (dest->Chain) { + auto& last = dest->Chain.GetLastChunk(); + if (last.Backend == first->Backend && last.End == first->Begin) { + last.End = first->End; + first = Chain.Erase(first); // TODO(alexvru): "it" gets invalidated here on some containers + } + } + dest->Chain.Splice(dest->Chain.end(), Chain, first, it); + } + if (num) { + auto it = Chain.begin(); + if (dest->Chain) { + auto& last = dest->Chain.GetLastChunk(); + if (last.Backend == first->Backend && last.End == first->Begin) { + first->Begin += num; + last.End = first->Begin; + return; + } + } + dest->Chain.PutToEnd(TChunk::Slice, it->Begin, it->Begin + num, *it); + it->Begin += num; + } + } + + void Insert(TIterator pos, TRope&& rope) { + Y_VERIFY_DEBUG(this == pos.Rope); + Y_VERIFY_DEBUG(this != &rope); + + if (!rope) { + return; // do nothing for empty rope + } + + // adjust size + Size += std::exchange(rope.Size, 0); + + // check if we have to split the block + if (pos.PointsToChunkMiddle()) { + pos.Iter = Chain.InsertBefore(pos.Iter, TChunk::Slice, pos->Begin, pos.Ptr, pos.GetChunk()); + ++pos.Iter; + pos->Begin = pos.Ptr; + } + + // perform glueing if possible + TChunk *ropeLeft = &rope.Chain.GetFirstChunk(); + TChunk *ropeRight = &rope.Chain.GetLastChunk(); + bool gluedLeft = false, gluedRight = false; + if (pos.Iter != Chain.begin()) { // glue left part whenever possible + // obtain iterator to previous chunk + auto prev(pos.Iter); + --prev; + if (prev->End == ropeLeft->Begin && prev->Backend == ropeLeft->Backend) { // it is glueable + prev->End = ropeLeft->End; + gluedLeft = true; + } + } + if (pos.Iter != Chain.end() && ropeRight->End == pos->Begin && ropeRight->Backend == pos->Backend) { + pos->Begin = ropeRight->Begin; + gluedRight = true; + } + if (gluedLeft) { + rope.Chain.EraseFront(); + } + if (gluedRight) { + if (rope) { + rope.Chain.EraseBack(); + } else { // it looks like double-glueing for the same chunk, we have to drop previous one + auto prev(pos.Iter); + --prev; + pos->Begin = prev->Begin; + pos.Iter = Chain.Erase(prev); + } + } + if (rope) { // insert remains + Chain.Splice(pos.Iter, rope.Chain, rope.Chain.begin(), rope.Chain.end()); + } + Y_VERIFY_DEBUG(!rope); + InvalidateIterators(); + } + + void EraseFront(size_t len) { + Y_VERIFY_DEBUG(Size >= len); + Size -= len; + + while (len) { + Y_VERIFY_DEBUG(Chain); + TChunk& item = Chain.GetFirstChunk(); + const size_t itemSize = item.GetSize(); + if (len >= itemSize) { + Chain.EraseFront(); + len -= itemSize; + } else { + item.Begin += len; + break; + } + } + + InvalidateIterators(); + } + + void EraseBack(size_t len) { + Y_VERIFY_DEBUG(Size >= len); + Size -= len; + + while (len) { + Y_VERIFY_DEBUG(Chain); + TChunk& item = Chain.GetLastChunk(); + const size_t itemSize = item.GetSize(); + if (len >= itemSize) { + Chain.EraseBack(); + len -= itemSize; + } else { + item.End -= len; + break; + } + } + + InvalidateIterators(); + } + + bool ExtractFrontPlain(void *buffer, size_t len) { + // check if we have enough data in the rope + if (Size < len) { + return false; + } + Size -= len; + while (len) { + auto& chunk = Chain.GetFirstChunk(); + const size_t num = Min(len, chunk.GetSize()); + memcpy(buffer, chunk.Begin, num); + buffer = static_cast<char*>(buffer) + num; + len -= num; + chunk.Begin += num; + if (chunk.Begin == chunk.End) { + Chain.Erase(Chain.begin()); + } + } + InvalidateIterators(); + return true; + } + + bool FetchFrontPlain(char **ptr, size_t *remain) { + const size_t num = Min(*remain, Size); + ExtractFrontPlain(*ptr, num); + *ptr += num; + *remain -= num; + return !*remain; + } + + static int Compare(const TRope& x, const TRope& y) { + TConstIterator xIter = x.Begin(), yIter = y.Begin(); + while (xIter.Valid() && yIter.Valid()) { + const size_t step = std::min(xIter.ContiguousSize(), yIter.ContiguousSize()); + if (int res = memcmp(xIter.ContiguousData(), yIter.ContiguousData(), step)) { + return res; + } + xIter += step; + yIter += step; + } + return xIter.Valid() - yIter.Valid(); + } + + // Use this method carefully -- it may significantly reduce performance when misused. + TString ConvertToString() const { + TString res = TString::Uninitialized(GetSize()); + Begin().ExtractPlainDataAndAdvance(res.Detach(), res.size()); + return res; + } + + TString DebugString() const { + TStringStream s; + s << "{Size# " << Size; + for (const auto& chunk : Chain) { + const char *data; + std::tie(data, std::ignore) = chunk.Backend.GetData(); + s << " [" << chunk.Begin - data << ", " << chunk.End - data << ")@" << chunk.Backend.UniqueId(); + } + s << "}"; + return s.Str(); + } + + friend bool operator==(const TRope& x, const TRope& y) { return Compare(x, y) == 0; } + friend bool operator!=(const TRope& x, const TRope& y) { return Compare(x, y) != 0; } + friend bool operator< (const TRope& x, const TRope& y) { return Compare(x, y) < 0; } + friend bool operator<=(const TRope& x, const TRope& y) { return Compare(x, y) <= 0; } + friend bool operator> (const TRope& x, const TRope& y) { return Compare(x, y) > 0; } + friend bool operator>=(const TRope& x, const TRope& y) { return Compare(x, y) >= 0; } + +private: + void Cut(TIterator begin, TIterator end, TRope *target) { + // ensure all iterators are belong to us + Y_VERIFY_DEBUG(this == begin.Rope && this == end.Rope); + + // if begin and end are equal, we do nothing -- checking this case allows us to find out that begin does not + // point to End(), for example + if (begin == end) { + return; + } + + auto addBlock = [&](const TChunk& from, const char *begin, const char *end) { + if (target) { + target->Chain.PutToEnd(TChunk::Slice, begin, end, from); + target->Size += end - begin; + } + Size -= end - begin; + }; + + // consider special case -- when begin and end point to the same block; in this case we have to split up this + // block into two parts + if (begin.Iter == end.Iter) { + addBlock(begin.GetChunk(), begin.Ptr, end.Ptr); + const char *firstChunkBegin = begin.PointsToChunkMiddle() ? begin->Begin : nullptr; + begin->Begin = end.Ptr; // this affects both begin and end iterator pointed values + if (firstChunkBegin) { + Chain.InsertBefore(begin.Iter, TChunk::Slice, firstChunkBegin, begin.Ptr, begin.GetChunk()); + } + } else { + // check the first iterator -- if it starts not from the begin of the block, we have to adjust end of the + // first block to match begin iterator and switch to next block + if (begin.PointsToChunkMiddle()) { + addBlock(begin.GetChunk(), begin.Ptr, begin->End); + begin->End = begin.Ptr; + begin.AdvanceToNextContiguousBlock(); + } + + // now drop full blocks + size_t rangeSize = 0; + for (auto it = begin.Iter; it != end.Iter; ++it) { + Y_VERIFY_DEBUG(it->GetSize()); + rangeSize += it->GetSize(); + } + if (rangeSize) { + if (target) { + end.Iter = target->Chain.Splice(target->Chain.end(), Chain, begin.Iter, end.Iter); + target->Size += rangeSize; + } else { + end.Iter = Chain.Erase(begin.Iter, end.Iter); + } + Size -= rangeSize; + } + + // and cut the last block if necessary + if (end.PointsToChunkMiddle()) { + addBlock(end.GetChunk(), end->Begin, end.Ptr); + end->Begin = end.Ptr; + } + } + + InvalidateIterators(); + } +}; + +class TRopeArena { + using TAllocateCallback = std::function<TIntrusivePtr<IRopeChunkBackend>()>; + + TAllocateCallback Allocator; + TRope Arena; + size_t Size = 0; + THashSet<const void*> AccountedBuffers; + +public: + TRopeArena(TAllocateCallback&& allocator) + : Allocator(std::move(allocator)) + {} + + TRope CreateRope(const void *buffer, size_t len) { + TRope res; + + while (len) { + if (Arena) { + auto iter = Arena.Begin(); + Y_VERIFY_DEBUG(iter.Valid()); + char *dest = const_cast<char*>(iter.ContiguousData()); + const size_t bytesToCopy = std::min(len, iter.ContiguousSize()); + memcpy(dest, buffer, bytesToCopy); + buffer = static_cast<const char*>(buffer) + bytesToCopy; + len -= bytesToCopy; + res.Insert(res.End(), Arena.Extract(Arena.Begin(), Arena.Position(bytesToCopy))); + } else { + Arena.Insert(Arena.End(), TRope(Allocator())); + } + } + + // align arena on 8-byte boundary + const size_t align = 8; + if (const size_t padding = Arena.GetSize() % align) { + Arena.EraseFront(padding); + } + + return res; + } + + size_t GetSize() const { + return Size; + } + + void AccountChunk(const TRope::TChunk& chunk) { + if (AccountedBuffers.insert(chunk.Backend.UniqueId()).second) { + Size += chunk.GetCapacity(); + } + } +}; + struct TRopeUtils { static void Memset(TRope::TConstIterator dst, char c, size_t size) { while (size) { @@ -1117,24 +1117,24 @@ public: } }; -inline TRope TRope::CopySpaceOptimized(TRope&& origin, size_t worstRatioPer1k, TRopeArena& arena) { - TRope res; - for (TChunk& chunk : origin.Chain) { - size_t ratio = chunk.GetSize() * 1024 / chunk.GetCapacity(); - if (ratio < 1024 - worstRatioPer1k) { - res.Insert(res.End(), arena.CreateRope(chunk.Begin, chunk.GetSize())); - } else { - res.Chain.PutToEnd(std::move(chunk)); - } - } - res.Size = origin.Size; - origin = TRope(); - for (const TChunk& chunk : res.Chain) { - arena.AccountChunk(chunk); - } - return res; -} - +inline TRope TRope::CopySpaceOptimized(TRope&& origin, size_t worstRatioPer1k, TRopeArena& arena) { + TRope res; + for (TChunk& chunk : origin.Chain) { + size_t ratio = chunk.GetSize() * 1024 / chunk.GetCapacity(); + if (ratio < 1024 - worstRatioPer1k) { + res.Insert(res.End(), arena.CreateRope(chunk.Begin, chunk.GetSize())); + } else { + res.Chain.PutToEnd(std::move(chunk)); + } + } + res.Size = origin.Size; + origin = TRope(); + for (const TChunk& chunk : res.Chain) { + arena.AccountChunk(chunk); + } + return res; +} + #if defined(WITH_VALGRIND) || defined(_msan_enabled_) |