Commit 39f46faa by Abseil Team Committed by rogeeff

Export of internal Abseil changes

--
83e4cdf03a4d702b30e69204060de09e462e23c6 by Greg Falcon <gfalcon@google.com>:

Revert the crc addition to RefcountAndFlags, and restore related comments to their original state.

In development, the implementation of SetExpectedCrc() changed, and there is no longer a need to track the CRC status in the refcount.

Since the distinction between IsOne() and IsMutable() is subtle *and unused*, removing it now can help avoid subtle bugs in the future.  This distinction can always be added back later, if it proves necessary.

Keep the reserved bit for now; all it costs is one extra mask instruction in the refcount checks, and space for extra state in Cord is always hard to find.

PiperOrigin-RevId: 408647038

--
ee67585cf66954176615271f50f8b278119dd138 by Greg Falcon <gfalcon@google.com>:

Implement Cord::SetExpectedChecksum() and Cord::ExpectedChecksum().

SetExpectedChecksum() will store a uint32_t out-of-band alongside a Cord's data.  This value persists through copies and assignments.  Mutating operations on a Cord cause the value to be forgotten.  ExpectedChecksum() retrieves the stored value, if present.

This API is intended for storing a CRC32C checksum alongside data, allowing checksums to be passed through dataflows and validated at the final step.  However, this API is agnostic to the meaning of the stored value.  No CRC32C validation is performed by these new APIs.

This implementation adds a new CordRep node, CordRepCrc.  A CordRepCrc may (currently) only live at the top of a tree.  This allows traversal logic to be agnostic to these nodes, instead putting the needed branches at the mutation level.    This also implements the property requested from API review, that any mutation is guaranteed to permanently forget the stored CRC.

PiperOrigin-RevId: 408611221

--
a86f592402b37c854ebdc77d2b9b425451a7a675 by Martijn Vels <mvels@google.com>:

Move 'ExtractResult' into CordRep

The result of an extract operation is logically identical for any tree implementation, and having a single type makes 'tree independent' implementation in cord.cc more concise.

PiperOrigin-RevId: 408332408

--
baa7647e21db59a87f75af9cac62172ce38a0f71 by Abseil Team <absl-team@google.com>:

Replace usages of `assert` macros with `ABSL_HARDENING_ASSERT`.

PiperOrigin-RevId: 408272133

--
c7658133d8662c39fa5035fc93a364c7c3d327e0 by Martijn Vels <mvels@google.com>:

Add CordRepBtree::ExtractAppendBuffer

PiperOrigin-RevId: 407944179

--
5775100363b5890ebfe710fadebf040445eab991 by Martijn Vels <mvels@google.com>:

Add CordRepConcat::ExtractAppendBuffer

PiperOrigin-RevId: 407932968

--
9f520ba1600a93352c78f644a369c7c76195ee86 by Greg Falcon <gfalcon@google.com>:

Add cordz tracking for crc nodes.

This also adds a new kSetExpectedChecksum method to the list of tracked methods.  This is presently unused but will be used soon.

PiperOrigin-RevId: 407884120
GitOrigin-RevId: 83e4cdf03a4d702b30e69204060de09e462e23c6
Change-Id: I134ace2d87215813eaa60a282996a33884676c06
parent c86347d4
...@@ -210,6 +210,7 @@ set(ABSL_INTERNAL_DLL_FILES ...@@ -210,6 +210,7 @@ set(ABSL_INTERNAL_DLL_FILES
"strings/internal/cord_rep_btree_navigator.h" "strings/internal/cord_rep_btree_navigator.h"
"strings/internal/cord_rep_btree_reader.cc" "strings/internal/cord_rep_btree_reader.cc"
"strings/internal/cord_rep_btree_reader.h" "strings/internal/cord_rep_btree_reader.h"
"strings/internal/cord_rep_concat.cc"
"strings/internal/cord_rep_crc.cc" "strings/internal/cord_rep_crc.cc"
"strings/internal/cord_rep_crc.h" "strings/internal/cord_rep_crc.h"
"strings/internal/cord_rep_consume.h" "strings/internal/cord_rep_consume.h"
......
...@@ -36,7 +36,6 @@ ...@@ -36,7 +36,6 @@
#define ABSL_CONTAINER_INLINED_VECTOR_H_ #define ABSL_CONTAINER_INLINED_VECTOR_H_
#include <algorithm> #include <algorithm>
#include <cassert>
#include <cstddef> #include <cstddef>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
......
...@@ -430,7 +430,7 @@ class Storage { ...@@ -430,7 +430,7 @@ class Storage {
} }
void SubtractSize(SizeType<A> count) { void SubtractSize(SizeType<A> count) {
assert(count <= GetSize()); ABSL_HARDENING_ASSERT(count <= GetSize());
GetSizeAndIsAllocated() -= count << static_cast<SizeType<A>>(1); GetSizeAndIsAllocated() -= count << static_cast<SizeType<A>>(1);
} }
...@@ -441,7 +441,8 @@ class Storage { ...@@ -441,7 +441,8 @@ class Storage {
} }
void MemcpyFrom(const Storage& other_storage) { void MemcpyFrom(const Storage& other_storage) {
assert(IsMemcpyOk<A>::value || other_storage.GetIsAllocated()); ABSL_HARDENING_ASSERT(IsMemcpyOk<A>::value ||
other_storage.GetIsAllocated());
GetSizeAndIsAllocated() = other_storage.GetSizeAndIsAllocated(); GetSizeAndIsAllocated() = other_storage.GetSizeAndIsAllocated();
data_ = other_storage.data_; data_ = other_storage.data_;
...@@ -490,7 +491,7 @@ void Storage<T, N, A>::DestroyContents() { ...@@ -490,7 +491,7 @@ void Storage<T, N, A>::DestroyContents() {
template <typename T, size_t N, typename A> template <typename T, size_t N, typename A>
void Storage<T, N, A>::InitFrom(const Storage& other) { void Storage<T, N, A>::InitFrom(const Storage& other) {
const SizeType<A> n = other.GetSize(); const SizeType<A> n = other.GetSize();
assert(n > 0); // Empty sources handled handled in caller. ABSL_HARDENING_ASSERT(n > 0); // Empty sources handled handled in caller.
ConstPointer<A> src; ConstPointer<A> src;
Pointer<A> dst; Pointer<A> dst;
if (!other.GetIsAllocated()) { if (!other.GetIsAllocated()) {
...@@ -522,8 +523,8 @@ template <typename ValueAdapter> ...@@ -522,8 +523,8 @@ template <typename ValueAdapter>
auto Storage<T, N, A>::Initialize(ValueAdapter values, SizeType<A> new_size) auto Storage<T, N, A>::Initialize(ValueAdapter values, SizeType<A> new_size)
-> void { -> void {
// Only callable from constructors! // Only callable from constructors!
assert(!GetIsAllocated()); ABSL_HARDENING_ASSERT(!GetIsAllocated());
assert(GetSize() == 0); ABSL_HARDENING_ASSERT(GetSize() == 0);
Pointer<A> construct_data; Pointer<A> construct_data;
if (new_size > GetInlinedCapacity()) { if (new_size > GetInlinedCapacity()) {
...@@ -832,7 +833,7 @@ auto Storage<T, N, A>::Reserve(SizeType<A> requested_capacity) -> void { ...@@ -832,7 +833,7 @@ auto Storage<T, N, A>::Reserve(SizeType<A> requested_capacity) -> void {
template <typename T, size_t N, typename A> template <typename T, size_t N, typename A>
auto Storage<T, N, A>::ShrinkToFit() -> void { auto Storage<T, N, A>::ShrinkToFit() -> void {
// May only be called on allocated instances! // May only be called on allocated instances!
assert(GetIsAllocated()); ABSL_HARDENING_ASSERT(GetIsAllocated());
StorageView<A> storage_view{GetAllocatedData(), GetSize(), StorageView<A> storage_view{GetAllocatedData(), GetSize(),
GetAllocatedCapacity()}; GetAllocatedCapacity()};
...@@ -881,7 +882,7 @@ auto Storage<T, N, A>::ShrinkToFit() -> void { ...@@ -881,7 +882,7 @@ auto Storage<T, N, A>::ShrinkToFit() -> void {
template <typename T, size_t N, typename A> template <typename T, size_t N, typename A>
auto Storage<T, N, A>::Swap(Storage* other_storage_ptr) -> void { auto Storage<T, N, A>::Swap(Storage* other_storage_ptr) -> void {
using std::swap; using std::swap;
assert(this != other_storage_ptr); ABSL_HARDENING_ASSERT(this != other_storage_ptr);
if (GetIsAllocated() && other_storage_ptr->GetIsAllocated()) { if (GetIsAllocated() && other_storage_ptr->GetIsAllocated()) {
swap(data_.allocated, other_storage_ptr->data_.allocated); swap(data_.allocated, other_storage_ptr->data_.allocated);
......
...@@ -271,6 +271,7 @@ cc_library( ...@@ -271,6 +271,7 @@ cc_library(
"internal/cord_rep_btree.cc", "internal/cord_rep_btree.cc",
"internal/cord_rep_btree_navigator.cc", "internal/cord_rep_btree_navigator.cc",
"internal/cord_rep_btree_reader.cc", "internal/cord_rep_btree_reader.cc",
"internal/cord_rep_concat.cc",
"internal/cord_rep_consume.cc", "internal/cord_rep_consume.cc",
"internal/cord_rep_crc.cc", "internal/cord_rep_crc.cc",
"internal/cord_rep_ring.cc", "internal/cord_rep_ring.cc",
...@@ -308,12 +309,15 @@ cc_library( ...@@ -308,12 +309,15 @@ cc_library(
) )
cc_test( cc_test(
name = "cord_internal_test", name = "cord_rep_concat_test",
srcs = ["internal/cord_internal_test.cc"], size = "small",
srcs = ["internal/cord_rep_concat_test.cc"],
copts = ABSL_TEST_COPTS, copts = ABSL_TEST_COPTS,
visibility = ["//visibility:private"], visibility = ["//visibility:private"],
deps = [ deps = [
":cord_internal", ":cord_internal",
":cord_rep_test_util",
"//absl/base:config",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
], ],
) )
...@@ -711,6 +715,7 @@ cc_test( ...@@ -711,6 +715,7 @@ cc_test(
"//absl/base:endian", "//absl/base:endian",
"//absl/base:raw_logging_internal", "//absl/base:raw_logging_internal",
"//absl/container:fixed_array", "//absl/container:fixed_array",
"//absl/hash",
"//absl/random", "//absl/random",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
], ],
......
...@@ -568,6 +568,7 @@ absl_cc_library( ...@@ -568,6 +568,7 @@ absl_cc_library(
"internal/cord_rep_btree.cc" "internal/cord_rep_btree.cc"
"internal/cord_rep_btree_navigator.cc" "internal/cord_rep_btree_navigator.cc"
"internal/cord_rep_btree_reader.cc" "internal/cord_rep_btree_reader.cc"
"internal/cord_rep_concat.cc"
"internal/cord_rep_crc.cc" "internal/cord_rep_crc.cc"
"internal/cord_rep_consume.cc" "internal/cord_rep_consume.cc"
"internal/cord_rep_ring.cc" "internal/cord_rep_ring.cc"
...@@ -922,6 +923,7 @@ absl_cc_test( ...@@ -922,6 +923,7 @@ absl_cc_test(
absl::cordz_test_helpers absl::cordz_test_helpers
absl::core_headers absl::core_headers
absl::endian absl::endian
absl::hash
absl::random_random absl::random_random
absl::raw_logging_internal absl::raw_logging_internal
absl::fixed_array absl::fixed_array
...@@ -948,13 +950,17 @@ absl_cc_test( ...@@ -948,13 +950,17 @@ absl_cc_test(
absl_cc_test( absl_cc_test(
NAME NAME
cord_internal_test cord_rep_concat_test
SRCS SRCS
"internal/cord_internal_test.cc" "internal/cord_rep_concat_test.cc"
COPTS COPTS
${ABSL_TEST_COPTS} ${ABSL_TEST_COPTS}
DEPS DEPS
absl::base
absl::config
absl::cord_internal absl::cord_internal
absl::cord_rep_test_util
absl::core_headers
GTest::gmock_main GTest::gmock_main
) )
......
...@@ -81,6 +81,7 @@ ...@@ -81,6 +81,7 @@
#include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_btree.h" #include "absl/strings/internal/cord_rep_btree.h"
#include "absl/strings/internal/cord_rep_btree_reader.h" #include "absl/strings/internal/cord_rep_btree_reader.h"
#include "absl/strings/internal/cord_rep_crc.h"
#include "absl/strings/internal/cord_rep_ring.h" #include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/internal/cordz_functions.h" #include "absl/strings/internal/cordz_functions.h"
#include "absl/strings/internal/cordz_info.h" #include "absl/strings/internal/cordz_info.h"
...@@ -671,6 +672,29 @@ class Cord { ...@@ -671,6 +672,29 @@ class Cord {
cord->Append(part); cord->Append(part);
} }
// Cord::SetExpectedChecksum()
//
// Stores a checksum value with this non-empty cord instance, for later
// retrieval.
//
// The expected checksum is a number stored out-of-band, alongside the data.
// It is preserved across copies and assignments, but any mutations to a cord
// will cause it to lose its expected checksum.
//
// The expected checksum is not part of a Cord's value, and does not affect
// operations such as equality or hashing.
//
// This field is intended to store a CRC32C checksum for later validation, to
// help support end-to-end checksum workflows. However, the Cord API itself
// does no CRC validation, and assigns no meaning to this number.
//
// This call has no effect if this cord is empty.
void SetExpectedChecksum(uint32_t crc);
// Returns this cord's expected checksum, if it has one. Otherwise, returns
// nullopt.
absl::optional<uint32_t> ExpectedChecksum() const;
template <typename H> template <typename H>
friend H AbslHashValue(H hash_state, const absl::Cord& c) { friend H AbslHashValue(H hash_state, const absl::Cord& c) {
absl::optional<absl::string_view> maybe_flat = c.TryFlat(); absl::optional<absl::string_view> maybe_flat = c.TryFlat();
...@@ -1274,6 +1298,7 @@ inline bool Cord::StartsWith(absl::string_view rhs) const { ...@@ -1274,6 +1298,7 @@ inline bool Cord::StartsWith(absl::string_view rhs) const {
} }
inline void Cord::ChunkIterator::InitTree(cord_internal::CordRep* tree) { inline void Cord::ChunkIterator::InitTree(cord_internal::CordRep* tree) {
tree = cord_internal::SkipCrcNode(tree);
if (tree->tag == cord_internal::BTREE) { if (tree->tag == cord_internal::BTREE) {
current_chunk_ = btree_reader_.Init(tree->btree()); current_chunk_ = btree_reader_.Init(tree->btree());
return; return;
......
...@@ -87,9 +87,6 @@ class RefcountAndFlags { ...@@ -87,9 +87,6 @@ class RefcountAndFlags {
constexpr RefcountAndFlags() : count_{kRefIncrement} {} constexpr RefcountAndFlags() : count_{kRefIncrement} {}
struct Immortal {}; struct Immortal {};
explicit constexpr RefcountAndFlags(Immortal) : count_(kImmortalFlag) {} explicit constexpr RefcountAndFlags(Immortal) : count_(kImmortalFlag) {}
struct WithCrc {};
explicit constexpr RefcountAndFlags(WithCrc)
: count_(kCrcFlag | kRefIncrement) {}
// Increments the reference count. Imposes no memory ordering. // Increments the reference count. Imposes no memory ordering.
inline void Increment() { inline void Increment() {
...@@ -125,32 +122,14 @@ class RefcountAndFlags { ...@@ -125,32 +122,14 @@ class RefcountAndFlags {
return count_.load(std::memory_order_acquire) >> kNumFlags; return count_.load(std::memory_order_acquire) >> kNumFlags;
} }
// Returns true if the referenced object carries a CRC value. // Returns whether the atomic integer is 1.
bool HasCrc() const { // If the reference count is used in the conventional way, a
return (count_.load(std::memory_order_relaxed) & kCrcFlag) != 0; // reference count of 1 implies that the current thread owns the
} // reference and no other thread shares it.
// This call performs the test for a reference count of one, and
// Returns true iff the atomic integer is 1 and this node does not store // performs the memory barrier needed for the owning thread
// a CRC. When both these conditions are met, the current thread owns // to act on the object, knowing that it has exclusive access to the
// the reference and no other thread shares it, so its contents may be // object. Always returns false when the immortal bit is set.
// safely mutated.
//
// If the referenced item is shared, carries a CRC, or is immortal,
// it should not be modified in-place, and this function returns false.
//
// This call performs the memory barrier needed for the owning thread
// to act on the object, so that if it returns true, it may safely
// assume exclusive access to the object.
inline bool IsMutable() {
return (count_.load(std::memory_order_acquire)) == kRefIncrement;
}
// Returns whether the atomic integer is 1. Similar to IsMutable(),
// but does not check for a stored CRC. (An unshared node with a CRC is not
// mutable, because changing its data would invalidate the CRC.)
//
// When this returns true, there are no other references, and data sinks
// may safely adopt the children of the CordRep.
inline bool IsOne() { inline bool IsOne() {
return (count_.load(std::memory_order_acquire) & kRefcountMask) == return (count_.load(std::memory_order_acquire) & kRefcountMask) ==
kRefIncrement; kRefIncrement;
...@@ -170,14 +149,14 @@ class RefcountAndFlags { ...@@ -170,14 +149,14 @@ class RefcountAndFlags {
kNumFlags = 2, kNumFlags = 2,
kImmortalFlag = 0x1, kImmortalFlag = 0x1,
kCrcFlag = 0x2, kReservedFlag = 0x2,
kRefIncrement = (1 << kNumFlags), kRefIncrement = (1 << kNumFlags),
// Bitmask to use when checking refcount by equality. This masks out // Bitmask to use when checking refcount by equality. This masks out
// all flags except kImmortalFlag, which is part of the refcount for // all flags except kImmortalFlag, which is part of the refcount for
// purposes of equality. (A refcount of 0 or 1 does not count as 0 or 1 // purposes of equality. (A refcount of 0 or 1 does not count as 0 or 1
// if the immortal bit is set.) // if the immortal bit is set.)
kRefcountMask = ~kCrcFlag, kRefcountMask = ~kReservedFlag,
}; };
std::atomic<int32_t> count_; std::atomic<int32_t> count_;
...@@ -227,6 +206,18 @@ static_assert(EXTERNAL == RING + 1, "BTREE and EXTERNAL not consecutive"); ...@@ -227,6 +206,18 @@ static_assert(EXTERNAL == RING + 1, "BTREE and EXTERNAL not consecutive");
static_assert(FLAT == EXTERNAL + 1, "EXTERNAL and FLAT not consecutive"); static_assert(FLAT == EXTERNAL + 1, "EXTERNAL and FLAT not consecutive");
struct CordRep { struct CordRep {
// Result from an `extract edge` operation. Contains the (possibly changed)
// tree node as well as the extracted edge, or {tree, nullptr} if no edge
// could be extracted.
// On success, the returned `tree` value is null if `extracted` was the only
// data edge inside the tree, a data edge if there were only two data edges in
// the tree, or the (possibly new / smaller) remaining tree with the extracted
// data edge removed.
struct ExtractResult {
CordRep* tree;
CordRep* extracted;
};
CordRep() = default; CordRep() = default;
constexpr CordRep(RefcountAndFlags::Immortal immortal, size_t l) constexpr CordRep(RefcountAndFlags::Immortal immortal, size_t l)
: length(l), refcount(immortal), tag(EXTERNAL), storage{} {} : length(l), refcount(immortal), tag(EXTERNAL), storage{} {}
...@@ -294,6 +285,13 @@ struct CordRepConcat : public CordRep { ...@@ -294,6 +285,13 @@ struct CordRepConcat : public CordRep {
uint8_t depth() const { return storage[0]; } uint8_t depth() const { return storage[0]; }
void set_depth(uint8_t depth) { storage[0] = depth; } void set_depth(uint8_t depth) { storage[0] = depth; }
// Extracts the right-most flat in the provided concat tree if the entire path
// to that flat is not shared, and the flat has the requested extra capacity.
// Returns the (potentially new) top level tree node and the extracted flat,
// or {tree, nullptr} if no flat was extracted.
static ExtractResult ExtractAppendBuffer(CordRepConcat* tree,
size_t extra_capacity);
}; };
struct CordRepSubstring : public CordRep { struct CordRepSubstring : public CordRep {
......
// Copyright 2021 The Abseil Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "absl/strings/internal/cord_internal.h"
#include "gmock/gmock.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
TEST(RefcountAndFlags, NormalRefcount) {
for (bool expect_high_refcount : {false, true}) {
SCOPED_TRACE(expect_high_refcount);
RefcountAndFlags refcount;
// count = 1
EXPECT_FALSE(refcount.HasCrc());
EXPECT_TRUE(refcount.IsMutable());
EXPECT_TRUE(refcount.IsOne());
refcount.Increment();
// count = 2
EXPECT_FALSE(refcount.HasCrc());
EXPECT_FALSE(refcount.IsMutable());
EXPECT_FALSE(refcount.IsOne());
// Decrementing should return true, since a reference is outstanding.
if (expect_high_refcount) {
EXPECT_TRUE(refcount.DecrementExpectHighRefcount());
} else {
EXPECT_TRUE(refcount.Decrement());
}
// count = 1
EXPECT_FALSE(refcount.HasCrc());
EXPECT_TRUE(refcount.IsMutable());
EXPECT_TRUE(refcount.IsOne());
// One more decremnt will return false, as no references remain.
if (expect_high_refcount) {
EXPECT_FALSE(refcount.DecrementExpectHighRefcount());
} else {
EXPECT_FALSE(refcount.Decrement());
}
}
}
TEST(RefcountAndFlags, CrcRefcount) {
for (bool expect_high_refcount : {false, true}) {
SCOPED_TRACE(expect_high_refcount);
RefcountAndFlags refcount(RefcountAndFlags::WithCrc{});
// count = 1
// A CRC-carrying node is never mutable, but can be unshared
EXPECT_TRUE(refcount.HasCrc());
EXPECT_FALSE(refcount.IsMutable());
EXPECT_TRUE(refcount.IsOne());
refcount.Increment();
// count = 2
EXPECT_TRUE(refcount.HasCrc());
EXPECT_FALSE(refcount.IsMutable());
EXPECT_FALSE(refcount.IsOne());
// Decrementing should return true, since a reference is outstanding.
if (expect_high_refcount) {
EXPECT_TRUE(refcount.DecrementExpectHighRefcount());
} else {
EXPECT_TRUE(refcount.Decrement());
}
// count = 1
EXPECT_TRUE(refcount.HasCrc());
EXPECT_FALSE(refcount.IsMutable());
EXPECT_TRUE(refcount.IsOne());
// One more decremnt will return false, as no references remain.
if (expect_high_refcount) {
EXPECT_FALSE(refcount.DecrementExpectHighRefcount());
} else {
EXPECT_FALSE(refcount.Decrement());
}
}
}
TEST(RefcountAndFlags, ImmortalRefcount) {
RefcountAndFlags immortal_refcount(RefcountAndFlags::Immortal{});
for (int i = 0; i < 100; ++i) {
// An immortal refcount is never unshared, and decrementing never causes
// a collection.
EXPECT_FALSE(immortal_refcount.HasCrc());
EXPECT_FALSE(immortal_refcount.IsMutable());
EXPECT_FALSE(immortal_refcount.IsOne());
EXPECT_TRUE(immortal_refcount.Decrement());
EXPECT_TRUE(immortal_refcount.DecrementExpectHighRefcount());
}
}
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
...@@ -216,8 +216,8 @@ void DeleteLeafEdge(CordRep* rep) { ...@@ -216,8 +216,8 @@ void DeleteLeafEdge(CordRep* rep) {
// propagate node changes up the stack. // propagate node changes up the stack.
template <EdgeType edge_type> template <EdgeType edge_type>
struct StackOperations { struct StackOperations {
// Returns true if the node at 'depth' is mutable, i.e. has a refcount // Returns true if the node at 'depth' is not shared, i.e. has a refcount
// of one, carries no CRC, and all of its parent nodes have a refcount of one. // of one and all of its parent nodes have a refcount of one.
inline bool owned(int depth) const { return depth < share_depth; } inline bool owned(int depth) const { return depth < share_depth; }
// Returns the node at 'depth'. // Returns the node at 'depth'.
...@@ -228,11 +228,11 @@ struct StackOperations { ...@@ -228,11 +228,11 @@ struct StackOperations {
inline CordRepBtree* BuildStack(CordRepBtree* tree, int depth) { inline CordRepBtree* BuildStack(CordRepBtree* tree, int depth) {
assert(depth <= tree->height()); assert(depth <= tree->height());
int current_depth = 0; int current_depth = 0;
while (current_depth < depth && tree->refcount.IsMutable()) { while (current_depth < depth && tree->refcount.IsOne()) {
stack[current_depth++] = tree; stack[current_depth++] = tree;
tree = tree->Edge(edge_type)->btree(); tree = tree->Edge(edge_type)->btree();
} }
share_depth = current_depth + (tree->refcount.IsMutable() ? 1 : 0); share_depth = current_depth + (tree->refcount.IsOne() ? 1 : 0);
while (current_depth < depth) { while (current_depth < depth) {
stack[current_depth++] = tree; stack[current_depth++] = tree;
tree = tree->Edge(edge_type)->btree(); tree = tree->Edge(edge_type)->btree();
...@@ -241,17 +241,17 @@ struct StackOperations { ...@@ -241,17 +241,17 @@ struct StackOperations {
} }
// Builds a stack with the invariant that all nodes are private owned / not // Builds a stack with the invariant that all nodes are private owned / not
// shared and carry no CRC data. This is used in iterative updates where a // shared. This is used in iterative updates where a previous propagation
// previous propagation guaranteed all nodes have this property. // guaranteed all nodes are owned / private.
inline void BuildOwnedStack(CordRepBtree* tree, int height) { inline void BuildOwnedStack(CordRepBtree* tree, int height) {
assert(height <= CordRepBtree::kMaxHeight); assert(height <= CordRepBtree::kMaxHeight);
int depth = 0; int depth = 0;
while (depth < height) { while (depth < height) {
assert(tree->refcount.IsMutable()); assert(tree->refcount.IsOne());
stack[depth++] = tree; stack[depth++] = tree;
tree = tree->Edge(edge_type)->btree(); tree = tree->Edge(edge_type)->btree();
} }
assert(tree->refcount.IsMutable()); assert(tree->refcount.IsOne());
share_depth = depth + 1; share_depth = depth + 1;
} }
...@@ -336,12 +336,12 @@ struct StackOperations { ...@@ -336,12 +336,12 @@ struct StackOperations {
return Unwind</*propagate=*/true>(tree, depth, length, result); return Unwind</*propagate=*/true>(tree, depth, length, result);
} }
// `share_depth` contains the depth at which the nodes in the stack cannot // `share_depth` contains the depth at which the nodes in the stack become
// be mutated. I.e., if the top most level is shared (i.e.: // shared. I.e., if the top most level is shared (i.e.: `!refcount.IsOne()`),
// `!refcount.IsMutable()`), then `share_depth` is 0. If the 2nd node // then `share_depth` is 0. If the 2nd node is shared (and implicitly all
// is shared (and implicitly all nodes below that) then `share_depth` is 1, // nodes below that) then `share_depth` is 1, etc. A `share_depth` greater
// etc. A `share_depth` greater than the depth of the stack indicates that // than the depth of the stack indicates that none of the nodes in the stack
// none of the nodes in the stack are shared. // are shared.
int share_depth; int share_depth;
NodeStack stack; NodeStack stack;
...@@ -773,7 +773,7 @@ CopyResult CordRepBtree::CopyPrefix(size_t n, bool allow_folding) { ...@@ -773,7 +773,7 @@ CopyResult CordRepBtree::CopyPrefix(size_t n, bool allow_folding) {
CordRep* CordRepBtree::ExtractFront(CordRepBtree* tree) { CordRep* CordRepBtree::ExtractFront(CordRepBtree* tree) {
CordRep* front = tree->Edge(tree->begin()); CordRep* front = tree->Edge(tree->begin());
if (tree->refcount.IsMutable()) { if (tree->refcount.IsOne()) {
Unref(tree->Edges(tree->begin() + 1, tree->end())); Unref(tree->Edges(tree->begin() + 1, tree->end()));
CordRepBtree::Delete(tree); CordRepBtree::Delete(tree);
} else { } else {
...@@ -786,7 +786,7 @@ CordRep* CordRepBtree::ExtractFront(CordRepBtree* tree) { ...@@ -786,7 +786,7 @@ CordRep* CordRepBtree::ExtractFront(CordRepBtree* tree) {
CordRepBtree* CordRepBtree::ConsumeBeginTo(CordRepBtree* tree, size_t end, CordRepBtree* CordRepBtree::ConsumeBeginTo(CordRepBtree* tree, size_t end,
size_t new_length) { size_t new_length) {
assert(end <= tree->end()); assert(end <= tree->end());
if (tree->refcount.IsMutable()) { if (tree->refcount.IsOne()) {
Unref(tree->Edges(end, tree->end())); Unref(tree->Edges(end, tree->end()));
tree->set_end(end); tree->set_end(end);
tree->length = new_length; tree->length = new_length;
...@@ -813,13 +813,13 @@ CordRep* CordRepBtree::RemoveSuffix(CordRepBtree* tree, size_t n) { ...@@ -813,13 +813,13 @@ CordRep* CordRepBtree::RemoveSuffix(CordRepBtree* tree, size_t n) {
size_t length = len - n; size_t length = len - n;
int height = tree->height(); int height = tree->height();
bool is_mutable = tree->refcount.IsMutable(); bool is_mutable = tree->refcount.IsOne();
// Extract all top nodes which are reduced to size = 1 // Extract all top nodes which are reduced to size = 1
Position pos = tree->IndexOfLength(length); Position pos = tree->IndexOfLength(length);
while (pos.index == tree->begin()) { while (pos.index == tree->begin()) {
CordRep* edge = ExtractFront(tree); CordRep* edge = ExtractFront(tree);
is_mutable &= edge->refcount.IsMutable(); is_mutable &= edge->refcount.IsOne();
if (height-- == 0) return ResizeEdge(edge, length, is_mutable); if (height-- == 0) return ResizeEdge(edge, length, is_mutable);
tree = edge->btree(); tree = edge->btree();
pos = tree->IndexOfLength(length); pos = tree->IndexOfLength(length);
...@@ -835,8 +835,8 @@ CordRep* CordRepBtree::RemoveSuffix(CordRepBtree* tree, size_t n) { ...@@ -835,8 +835,8 @@ CordRep* CordRepBtree::RemoveSuffix(CordRepBtree* tree, size_t n) {
length = pos.n; length = pos.n;
while (length != edge->length) { while (length != edge->length) {
// ConsumeBeginTo guarantees `tree` is a clean, privately owned copy. // ConsumeBeginTo guarantees `tree` is a clean, privately owned copy.
assert(tree->refcount.IsMutable()); assert(tree->refcount.IsOne());
const bool edge_is_mutable = edge->refcount.IsMutable(); const bool edge_is_mutable = edge->refcount.IsOne();
if (height-- == 0) { if (height-- == 0) {
tree->edges_[pos.index] = ResizeEdge(edge, length, edge_is_mutable); tree->edges_[pos.index] = ResizeEdge(edge, length, edge_is_mutable);
...@@ -973,7 +973,7 @@ char CordRepBtree::GetCharacter(size_t offset) const { ...@@ -973,7 +973,7 @@ char CordRepBtree::GetCharacter(size_t offset) const {
Span<char> CordRepBtree::GetAppendBufferSlow(size_t size) { Span<char> CordRepBtree::GetAppendBufferSlow(size_t size) {
// The inlined version in `GetAppendBuffer()` deals with all heights <= 3. // The inlined version in `GetAppendBuffer()` deals with all heights <= 3.
assert(height() >= 4); assert(height() >= 4);
assert(refcount.IsMutable()); assert(refcount.IsOne());
// Build a stack of nodes we may potentially need to update if we find a // Build a stack of nodes we may potentially need to update if we find a
// non-shared FLAT with capacity at the leaf level. // non-shared FLAT with capacity at the leaf level.
...@@ -982,13 +982,13 @@ Span<char> CordRepBtree::GetAppendBufferSlow(size_t size) { ...@@ -982,13 +982,13 @@ Span<char> CordRepBtree::GetAppendBufferSlow(size_t size) {
CordRepBtree* stack[kMaxDepth]; CordRepBtree* stack[kMaxDepth];
for (int i = 0; i < depth; ++i) { for (int i = 0; i < depth; ++i) {
node = node->Edge(kBack)->btree(); node = node->Edge(kBack)->btree();
if (!node->refcount.IsMutable()) return {}; if (!node->refcount.IsOne()) return {};
stack[i] = node; stack[i] = node;
} }
// Must be a privately owned, mutable flat. // Must be a privately owned, mutable flat.
CordRep* const edge = node->Edge(kBack); CordRep* const edge = node->Edge(kBack);
if (!edge->refcount.IsMutable() || edge->tag < FLAT) return {}; if (!edge->refcount.IsOne() || edge->tag < FLAT) return {};
// Must have capacity. // Must have capacity.
const size_t avail = edge->flat()->Capacity() - edge->length; const size_t avail = edge->flat()->Capacity() - edge->length;
...@@ -1123,6 +1123,79 @@ CordRepBtree* CordRepBtree::Rebuild(CordRepBtree* tree) { ...@@ -1123,6 +1123,79 @@ CordRepBtree* CordRepBtree::Rebuild(CordRepBtree* tree) {
return nullptr; return nullptr;
} }
CordRepBtree::ExtractResult CordRepBtree::ExtractAppendBuffer(
CordRepBtree* tree, size_t extra_capacity) {
int depth = 0;
NodeStack stack;
// Set up default 'no success' result which is {tree, nullptr}.
ExtractResult result;
result.tree = tree;
result.extracted = nullptr;
// Dive down the right side of the tree, making sure no edges are shared.
while (tree->height() > 0) {
if (!tree->refcount.IsOne()) return result;
stack[depth++] = tree;
tree = tree->Edge(kBack)->btree();
}
if (!tree->refcount.IsOne()) return result;
// Validate we ended on a non shared flat.
CordRep* rep = tree->Edge(kBack);
if (!(rep->IsFlat() && rep->refcount.IsOne())) return result;
// Verify it has at least the requested extra capacity.
CordRepFlat* flat = rep->flat();
const size_t length = flat->length;
const size_t avail = flat->Capacity() - flat->length;
if (extra_capacity > avail) return result;
// Set the extracted flat in the result.
result.extracted = flat;
// Cascading delete all nodes that become empty.
while (tree->size() == 1) {
CordRepBtree::Delete(tree);
if (--depth < 0) {
// We consumed the entire tree: return nullptr for new tree.
result.tree = nullptr;
return result;
}
rep = tree;
tree = stack[depth];
}
// Remove the edge or cascaded up parent node.
tree->set_end(tree->end() - 1);
tree->length -= length;
// Adjust lengths up the tree.
while (depth > 0) {
tree = stack[--depth];
tree->length -= length;
}
// Remove unnecessary top nodes with size = 1. This may iterate all the way
// down to the leaf node in which case we simply return the remaining last
// edge in that node and the extracted flat.
while (tree->size() == 1) {
int height = tree->height();
rep = tree->Edge(kBack);
Delete(tree);
if (height == 0) {
// We consumed the leaf: return the sole data edge as the new tree.
result.tree = rep;
return result;
}
tree = rep->btree();
}
// Done: return the (new) top level node and extracted flat.
result.tree = tree;
return result;
}
} // namespace cord_internal } // namespace cord_internal
ABSL_NAMESPACE_END ABSL_NAMESPACE_END
} // namespace absl } // namespace absl
...@@ -240,11 +240,41 @@ class CordRepBtree : public CordRep { ...@@ -240,11 +240,41 @@ class CordRepBtree : public CordRep {
// length of the flat node and involved tree nodes have been increased by // length of the flat node and involved tree nodes have been increased by
// `span.length()`. The caller is responsible for immediately assigning values // `span.length()`. The caller is responsible for immediately assigning values
// to all uninitialized data reference by the returned span. // to all uninitialized data reference by the returned span.
// Requires `this->refcount.IsMutable()`: this function forces the // Requires `this->refcount.IsOne()`: this function forces the caller to do
// caller to do this fast path check on the top level node, as this is the // this fast path check on the top level node, as this is the most commonly
// most commonly shared node of a cord tree. // shared node of a cord tree.
Span<char> GetAppendBuffer(size_t size); Span<char> GetAppendBuffer(size_t size);
// Extracts the right-most data edge from this tree iff:
// - the tree and all internal edges to the right-most node are not shared.
// - the right-most node is a FLAT node and not shared.
// - the right-most node has at least the desired extra capacity.
//
// Returns {tree, nullptr} if any of the above conditions are not met.
// This method effectively removes data from the tree. The intent of this
// method is to allow applications appending small string data to use
// pre-existing capacity, and add the modified rep back to the tree.
//
// Simplified such code would look similar to this:
// void MyTreeBuilder::Append(string_view data) {
// ExtractResult result = CordRepBtree::ExtractAppendBuffer(tree_, 1);
// if (CordRep* rep = result.extracted) {
// size_t available = rep->Capacity() - rep->length;
// size_t n = std::min(data.size(), n);
// memcpy(rep->Data(), data.data(), n);
// rep->length += n;
// data.remove_prefix(n);
// if (!result.tree->IsBtree()) {
// tree_ = CordRepBtree::Create(result.tree);
// }
// tree_ = CordRepBtree::Append(tree_, rep);
// }
// ...
// // Remaining edge in `result.tree`.
// }
static ExtractResult ExtractAppendBuffer(CordRepBtree* tree,
size_t extra_capacity = 1);
// Returns the `height` of the tree. The height of a tree is limited to // Returns the `height` of the tree. The height of a tree is limited to
// kMaxHeight. `height` is implemented as an `int` as in some places we // kMaxHeight. `height` is implemented as an `int` as in some places we
// use negative (-1) values for 'data edges'. // use negative (-1) values for 'data edges'.
...@@ -849,7 +879,7 @@ inline CordRepBtree* CordRepBtree::Create(CordRep* rep) { ...@@ -849,7 +879,7 @@ inline CordRepBtree* CordRepBtree::Create(CordRep* rep) {
} }
inline Span<char> CordRepBtree::GetAppendBuffer(size_t size) { inline Span<char> CordRepBtree::GetAppendBuffer(size_t size) {
assert(refcount.IsMutable()); assert(refcount.IsOne());
CordRepBtree* tree = this; CordRepBtree* tree = this;
const int height = this->height(); const int height = this->height();
CordRepBtree* n1 = tree; CordRepBtree* n1 = tree;
...@@ -858,21 +888,21 @@ inline Span<char> CordRepBtree::GetAppendBuffer(size_t size) { ...@@ -858,21 +888,21 @@ inline Span<char> CordRepBtree::GetAppendBuffer(size_t size) {
switch (height) { switch (height) {
case 3: case 3:
tree = tree->Edge(kBack)->btree(); tree = tree->Edge(kBack)->btree();
if (!tree->refcount.IsMutable()) return {}; if (!tree->refcount.IsOne()) return {};
n2 = tree; n2 = tree;
ABSL_FALLTHROUGH_INTENDED; ABSL_FALLTHROUGH_INTENDED;
case 2: case 2:
tree = tree->Edge(kBack)->btree(); tree = tree->Edge(kBack)->btree();
if (!tree->refcount.IsMutable()) return {}; if (!tree->refcount.IsOne()) return {};
n1 = tree; n1 = tree;
ABSL_FALLTHROUGH_INTENDED; ABSL_FALLTHROUGH_INTENDED;
case 1: case 1:
tree = tree->Edge(kBack)->btree(); tree = tree->Edge(kBack)->btree();
if (!tree->refcount.IsMutable()) return {}; if (!tree->refcount.IsOne()) return {};
ABSL_FALLTHROUGH_INTENDED; ABSL_FALLTHROUGH_INTENDED;
case 0: case 0:
CordRep* edge = tree->Edge(kBack); CordRep* edge = tree->Edge(kBack);
if (!edge->refcount.IsMutable()) return {}; if (!edge->refcount.IsOne()) return {};
if (edge->tag < FLAT) return {}; if (edge->tag < FLAT) return {};
size_t avail = edge->flat()->Capacity() - edge->length; size_t avail = edge->flat()->Capacity() - edge->length;
if (avail == 0) return {}; if (avail == 0) return {};
......
...@@ -128,6 +128,16 @@ MATCHER_P2(IsSubstring, start, length, ...@@ -128,6 +128,16 @@ MATCHER_P2(IsSubstring, start, length,
return true; return true;
} }
MATCHER_P2(EqExtractResult, tree, rep, "Equals ExtractResult") {
if (arg.tree != tree || arg.extracted != rep) {
*result_listener << "Expected {" << static_cast<const void*>(tree) << ", "
<< static_cast<const void*>(rep) << "}, got {" << arg.tree
<< ", " << arg.extracted << "}";
return false;
}
return true;
}
// DataConsumer is a simple helper class used by tests to 'consume' string // DataConsumer is a simple helper class used by tests to 'consume' string
// fragments from the provided input in forward or backward direction. // fragments from the provided input in forward or backward direction.
class DataConsumer { class DataConsumer {
...@@ -1483,6 +1493,125 @@ TEST_P(CordRepBtreeTest, Rebuild) { ...@@ -1483,6 +1493,125 @@ TEST_P(CordRepBtreeTest, Rebuild) {
} }
} }
// Convenience helper for CordRepBtree::ExtractAppendBuffer
CordRepBtree::ExtractResult ExtractLast(CordRepBtree* input, size_t cap = 1) {
return CordRepBtree::ExtractAppendBuffer(input, cap);
}
TEST(CordRepBtreeTest, ExtractAppendBufferLeafSingleFlat) {
CordRep* flat = MakeFlat("Abc");
CordRepBtree* leaf = CordRepBtree::Create(flat);
EXPECT_THAT(ExtractLast(leaf), EqExtractResult(nullptr, flat));
CordRep::Unref(flat);
}
TEST(CordRepBtreeTest, ExtractAppendBufferNodeSingleFlat) {
CordRep* flat = MakeFlat("Abc");
CordRepBtree* leaf = CordRepBtree::Create(flat);
CordRepBtree* node = CordRepBtree::New(leaf);
EXPECT_THAT(ExtractLast(node), EqExtractResult(nullptr, flat));
CordRep::Unref(flat);
}
TEST(CordRepBtreeTest, ExtractAppendBufferLeafTwoFlats) {
std::vector<CordRep*> flats = CreateFlatsFromString("abcdef", 3);
CordRepBtree* leaf = CreateTree(flats);
EXPECT_THAT(ExtractLast(leaf), EqExtractResult(flats[0], flats[1]));
CordRep::Unref(flats[0]);
CordRep::Unref(flats[1]);
}
TEST(CordRepBtreeTest, ExtractAppendBufferNodeTwoFlats) {
std::vector<CordRep*> flats = CreateFlatsFromString("abcdef", 3);
CordRepBtree* leaf = CreateTree(flats);
CordRepBtree* node = CordRepBtree::New(leaf);
EXPECT_THAT(ExtractLast(node), EqExtractResult(flats[0], flats[1]));
CordRep::Unref(flats[0]);
CordRep::Unref(flats[1]);
}
TEST(CordRepBtreeTest, ExtractAppendBufferNodeTwoFlatsInTwoLeafs) {
std::vector<CordRep*> flats = CreateFlatsFromString("abcdef", 3);
CordRepBtree* leaf1 = CordRepBtree::Create(flats[0]);
CordRepBtree* leaf2 = CordRepBtree::Create(flats[1]);
CordRepBtree* node = CordRepBtree::New(leaf1, leaf2);
EXPECT_THAT(ExtractLast(node), EqExtractResult(flats[0], flats[1]));
CordRep::Unref(flats[0]);
CordRep::Unref(flats[1]);
}
TEST(CordRepBtreeTest, ExtractAppendBufferLeafThreeFlats) {
std::vector<CordRep*> flats = CreateFlatsFromString("abcdefghi", 3);
CordRepBtree* leaf = CreateTree(flats);
EXPECT_THAT(ExtractLast(leaf), EqExtractResult(leaf, flats[2]));
CordRep::Unref(flats[2]);
CordRep::Unref(leaf);
}
TEST(CordRepBtreeTest, ExtractAppendBufferNodeThreeFlatsRightNoFolding) {
CordRep* flat = MakeFlat("Abc");
std::vector<CordRep*> flats = CreateFlatsFromString("defghi", 3);
CordRepBtree* leaf1 = CordRepBtree::Create(flat);
CordRepBtree* leaf2 = CreateTree(flats);
CordRepBtree* node = CordRepBtree::New(leaf1, leaf2);
EXPECT_THAT(ExtractLast(node), EqExtractResult(node, flats[1]));
EXPECT_THAT(node->Edges(), ElementsAre(leaf1, leaf2));
EXPECT_THAT(leaf1->Edges(), ElementsAre(flat));
EXPECT_THAT(leaf2->Edges(), ElementsAre(flats[0]));
CordRep::Unref(node);
CordRep::Unref(flats[1]);
}
TEST(CordRepBtreeTest, ExtractAppendBufferNodeThreeFlatsRightLeafFolding) {
CordRep* flat = MakeFlat("Abc");
std::vector<CordRep*> flats = CreateFlatsFromString("defghi", 3);
CordRepBtree* leaf1 = CreateTree(flats);
CordRepBtree* leaf2 = CordRepBtree::Create(flat);
CordRepBtree* node = CordRepBtree::New(leaf1, leaf2);
EXPECT_THAT(ExtractLast(node), EqExtractResult(leaf1, flat));
EXPECT_THAT(leaf1->Edges(), ElementsAreArray(flats));
CordRep::Unref(leaf1);
CordRep::Unref(flat);
}
TEST(CordRepBtreeTest, ExtractAppendBufferNoCapacity) {
std::vector<CordRep*> flats = CreateFlatsFromString("abcdef", 3);
CordRepBtree* leaf = CreateTree(flats);
size_t avail = flats[1]->flat()->Capacity() - flats[1]->length;
EXPECT_THAT(ExtractLast(leaf, avail + 1), EqExtractResult(leaf, nullptr));
EXPECT_THAT(ExtractLast(leaf, avail), EqExtractResult(flats[0], flats[1]));
CordRep::Unref(flats[0]);
CordRep::Unref(flats[1]);
}
TEST(CordRepBtreeTest, ExtractAppendBufferNotFlat) {
std::vector<CordRep*> flats = CreateFlatsFromString("abcdef", 3);
auto substr = MakeSubstring(1, 2, flats[1]);
CordRepBtree* leaf = CreateTree({flats[0], substr});
EXPECT_THAT(ExtractLast(leaf), EqExtractResult(leaf, nullptr));
CordRep::Unref(leaf);
}
TEST(CordRepBtreeTest, ExtractAppendBufferShared) {
std::vector<CordRep*> flats = CreateFlatsFromString("abcdef", 3);
CordRepBtree* leaf = CreateTree(flats);
CordRep::Ref(flats[1]);
EXPECT_THAT(ExtractLast(leaf), EqExtractResult(leaf, nullptr));
CordRep::Unref(flats[1]);
CordRep::Ref(leaf);
EXPECT_THAT(ExtractLast(leaf), EqExtractResult(leaf, nullptr));
CordRep::Unref(leaf);
CordRepBtree* node = CordRepBtree::New(leaf);
CordRep::Ref(node);
EXPECT_THAT(ExtractLast(node), EqExtractResult(node, nullptr));
CordRep::Unref(node);
CordRep::Unref(node);
}
} // namespace } // namespace
} // namespace cord_internal } // namespace cord_internal
ABSL_NAMESPACE_END ABSL_NAMESPACE_END
......
// Copyright 2021 The Abseil Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cstdint>
#include "absl/base/config.h"
#include "absl/container/inlined_vector.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_flat.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
CordRepConcat::ExtractResult CordRepConcat::ExtractAppendBuffer(
CordRepConcat* tree, size_t extra_capacity) {
absl::InlinedVector<CordRepConcat*, kInlinedVectorSize> stack;
CordRepConcat* concat = tree;
CordRep* rep = concat->right;
// Dive down the tree, making sure no edges are shared
while (concat->refcount.IsOne() && rep->IsConcat()) {
stack.push_back(concat);
concat = rep->concat();
rep = concat->right;
}
// Validate we ended on a non shared flat.
if (concat->refcount.IsOne() && rep->IsFlat() &&
rep->refcount.IsOne()) {
// Verify it has at least the requested extra capacity
CordRepFlat* flat = rep->flat();
size_t remaining = flat->Capacity() - flat->length;
if (extra_capacity > remaining) return {tree, nullptr};
// Check if we have a parent to adjust, or if we must return the left node.
rep = concat->left;
if (!stack.empty()) {
stack.back()->right = rep;
for (CordRepConcat* parent : stack) {
parent->length -= flat->length;
}
rep = tree;
}
delete concat;
return {rep, flat};
}
return {tree, nullptr};
}
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
// Copyright 2021 The Abseil Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cstdint>
#include <utility>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_test_util.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
namespace {
using ::absl::cordrep_testing::MakeFlat;
using ::absl::cordrep_testing::MakeSubstring;
using ::testing::Eq;
MATCHER_P2(EqExtractResult, tree, rep, "Equals ExtractResult") {
if (arg.tree != tree || arg.extracted != rep) {
*result_listener << "Expected {" << static_cast<const void*>(tree) << ", "
<< static_cast<const void*>(rep) << "}, got {" << arg.tree
<< ", " << arg.extracted << "}";
return false;
}
return true;
}
CordRepConcat* MakeConcat(CordRep* left, CordRep* right, int depth) {
CordRepConcat* concat = new CordRepConcat;
concat->tag = CONCAT;
concat->set_depth(depth);
concat->length = left->length + right->length;
concat->left = left;
concat->right = right;
return concat;
}
CordRepConcat::ExtractResult ExtractLast(CordRepConcat* concat,
size_t extra_capacity = 1) {
return CordRepConcat::ExtractAppendBuffer(concat, extra_capacity);
}
TEST(CordRepConcatTest, ExtractAppendBufferTwoFlats) {
CordRepFlat* flat1 = MakeFlat("abc");
CordRepFlat* flat2 = MakeFlat("defg");
CordRepConcat* concat = MakeConcat(flat1, flat2, 0);
EXPECT_THAT(ExtractLast(concat), EqExtractResult(flat1, flat2));
CordRep::Unref(flat1);
CordRep::Unref(flat2);
}
TEST(CordRepConcatTest, ExtractAppendBufferThreeFlatsOne) {
CordRepFlat* flat1 = MakeFlat("abc");
CordRepFlat* flat2 = MakeFlat("defg");
CordRepFlat* flat3 = MakeFlat("hijkl");
CordRepConcat* lconcat = MakeConcat(flat1, flat2, 0);
CordRepConcat* concat = MakeConcat(lconcat, flat3, 1);
EXPECT_THAT(ExtractLast(concat), EqExtractResult(lconcat, flat3));
ASSERT_THAT(lconcat->length, Eq(7));
CordRep::Unref(lconcat);
CordRep::Unref(flat3);
}
TEST(CordRepConcatTest, ExtractAppendBufferThreeFlatsTwo) {
CordRepFlat* flat1 = MakeFlat("hijkl");
CordRepFlat* flat2 = MakeFlat("abc");
CordRepFlat* flat3 = MakeFlat("defg");
CordRepConcat* rconcat = MakeConcat(flat2, flat3, 0);
CordRepConcat* concat = MakeConcat(flat1, rconcat, 1);
EXPECT_THAT(ExtractLast(concat), EqExtractResult(concat, flat3));
ASSERT_THAT(concat->length, Eq(8));
CordRep::Unref(concat);
CordRep::Unref(flat3);
}
TEST(CordRepConcatTest, ExtractAppendBufferShared) {
CordRepFlat* flat1 = MakeFlat("hijkl");
CordRepFlat* flat2 = MakeFlat("abc");
CordRepFlat* flat3 = MakeFlat("defg");
CordRepConcat* rconcat = MakeConcat(flat2, flat3, 0);
CordRepConcat* concat = MakeConcat(flat1, rconcat, 1);
CordRep::Ref(concat);
EXPECT_THAT(ExtractLast(concat), EqExtractResult(concat, nullptr));
CordRep::Unref(concat);
CordRep::Ref(rconcat);
EXPECT_THAT(ExtractLast(concat), EqExtractResult(concat, nullptr));
CordRep::Unref(rconcat);
CordRep::Ref(flat3);
EXPECT_THAT(ExtractLast(concat), EqExtractResult(concat, nullptr));
CordRep::Unref(flat3);
CordRep::Unref(concat);
}
TEST(CordRepConcatTest, ExtractAppendBufferNotFlat) {
CordRepFlat* flat1 = MakeFlat("hijkl");
CordRepFlat* flat2 = MakeFlat("abc");
CordRepFlat* flat3 = MakeFlat("defg");
auto substr = MakeSubstring(1, 2, flat3);
CordRepConcat* rconcat = MakeConcat(flat2, substr, 0);
CordRepConcat* concat = MakeConcat(flat1, rconcat, 1);
EXPECT_THAT(ExtractLast(concat), EqExtractResult(concat, nullptr));
CordRep::Unref(concat);
}
TEST(CordRepConcatTest, ExtractAppendBufferNoCapacity) {
CordRepFlat* flat1 = MakeFlat("hijkl");
CordRepFlat* flat2 = MakeFlat("abc");
CordRepFlat* flat3 = MakeFlat("defg");
size_t avail = flat3->Capacity() - flat3->length;
CordRepConcat* rconcat = MakeConcat(flat2, flat3, 0);
CordRepConcat* concat = MakeConcat(flat1, rconcat, 1);
// Should fail if 1 byte over, success if exactly matching
EXPECT_THAT(ExtractLast(concat, avail + 1), EqExtractResult(concat, nullptr));
EXPECT_THAT(ExtractLast(concat, avail), EqExtractResult(concat, flat3));
CordRep::Unref(concat);
CordRep::Unref(flat3);
}
} // namespace
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
...@@ -76,6 +76,15 @@ inline CordRep* SkipCrcNode(CordRep* rep) { ...@@ -76,6 +76,15 @@ inline CordRep* SkipCrcNode(CordRep* rep) {
} }
} }
inline const CordRep* SkipCrcNode(const CordRep* rep) {
assert(rep != nullptr);
if (ABSL_PREDICT_FALSE(rep->IsCrc())) {
return rep->crc()->child;
} else {
return rep;
}
}
inline CordRepCrc* CordRep::crc() { inline CordRepCrc* CordRep::crc() {
assert(IsCrc()); assert(IsCrc());
return static_cast<CordRepCrc*>(this); return static_cast<CordRepCrc*>(this);
......
...@@ -277,7 +277,7 @@ CordRepRing* CordRepRing::Mutable(CordRepRing* rep, size_t extra) { ...@@ -277,7 +277,7 @@ CordRepRing* CordRepRing::Mutable(CordRepRing* rep, size_t extra) {
// Get current number of entries, and check for max capacity. // Get current number of entries, and check for max capacity.
size_t entries = rep->entries(); size_t entries = rep->entries();
if (!rep->refcount.IsMutable()) { if (!rep->refcount.IsOne()) {
return Copy(rep, rep->head(), rep->tail(), extra); return Copy(rep, rep->head(), rep->tail(), extra);
} else if (entries + extra > rep->capacity()) { } else if (entries + extra > rep->capacity()) {
const size_t min_grow = rep->capacity() + rep->capacity() / 2; const size_t min_grow = rep->capacity() + rep->capacity() / 2;
...@@ -292,10 +292,10 @@ CordRepRing* CordRepRing::Mutable(CordRepRing* rep, size_t extra) { ...@@ -292,10 +292,10 @@ CordRepRing* CordRepRing::Mutable(CordRepRing* rep, size_t extra) {
} }
Span<char> CordRepRing::GetAppendBuffer(size_t size) { Span<char> CordRepRing::GetAppendBuffer(size_t size) {
assert(refcount.IsMutable()); assert(refcount.IsOne());
index_type back = retreat(tail_); index_type back = retreat(tail_);
CordRep* child = entry_child(back); CordRep* child = entry_child(back);
if (child->tag >= FLAT && child->refcount.IsMutable()) { if (child->tag >= FLAT && child->refcount.IsOne()) {
size_t capacity = child->flat()->Capacity(); size_t capacity = child->flat()->Capacity();
pos_type end_pos = entry_end_pos(back); pos_type end_pos = entry_end_pos(back);
size_t data_offset = entry_data_offset(back); size_t data_offset = entry_data_offset(back);
...@@ -312,10 +312,10 @@ Span<char> CordRepRing::GetAppendBuffer(size_t size) { ...@@ -312,10 +312,10 @@ Span<char> CordRepRing::GetAppendBuffer(size_t size) {
} }
Span<char> CordRepRing::GetPrependBuffer(size_t size) { Span<char> CordRepRing::GetPrependBuffer(size_t size) {
assert(refcount.IsMutable()); assert(refcount.IsOne());
CordRep* child = entry_child(head_); CordRep* child = entry_child(head_);
size_t data_offset = entry_data_offset(head_); size_t data_offset = entry_data_offset(head_);
if (data_offset && child->refcount.IsMutable() && child->tag >= FLAT) { if (data_offset && child->refcount.IsOne() && child->tag >= FLAT) {
size_t n = (std::min)(data_offset, size); size_t n = (std::min)(data_offset, size);
this->length += n; this->length += n;
begin_pos_ -= n; begin_pos_ -= n;
...@@ -504,7 +504,7 @@ CordRepRing* CordRepRing::Prepend(CordRepRing* rep, CordRep* child) { ...@@ -504,7 +504,7 @@ CordRepRing* CordRepRing::Prepend(CordRepRing* rep, CordRep* child) {
CordRepRing* CordRepRing::Append(CordRepRing* rep, absl::string_view data, CordRepRing* CordRepRing::Append(CordRepRing* rep, absl::string_view data,
size_t extra) { size_t extra) {
if (rep->refcount.IsMutable()) { if (rep->refcount.IsOne()) {
Span<char> avail = rep->GetAppendBuffer(data.length()); Span<char> avail = rep->GetAppendBuffer(data.length());
if (!avail.empty()) { if (!avail.empty()) {
memcpy(avail.data(), data.data(), avail.length()); memcpy(avail.data(), data.data(), avail.length());
...@@ -538,7 +538,7 @@ CordRepRing* CordRepRing::Append(CordRepRing* rep, absl::string_view data, ...@@ -538,7 +538,7 @@ CordRepRing* CordRepRing::Append(CordRepRing* rep, absl::string_view data,
CordRepRing* CordRepRing::Prepend(CordRepRing* rep, absl::string_view data, CordRepRing* CordRepRing::Prepend(CordRepRing* rep, absl::string_view data,
size_t extra) { size_t extra) {
if (rep->refcount.IsMutable()) { if (rep->refcount.IsOne()) {
Span<char> avail = rep->GetPrependBuffer(data.length()); Span<char> avail = rep->GetPrependBuffer(data.length());
if (!avail.empty()) { if (!avail.empty()) {
const char* tail = data.data() + data.length() - avail.length(); const char* tail = data.data() + data.length() - avail.length();
...@@ -678,7 +678,7 @@ CordRepRing* CordRepRing::SubRing(CordRepRing* rep, size_t offset, ...@@ -678,7 +678,7 @@ CordRepRing* CordRepRing::SubRing(CordRepRing* rep, size_t offset,
Position tail = rep->FindTail(head.index, offset + len); Position tail = rep->FindTail(head.index, offset + len);
const size_t new_entries = rep->entries(head.index, tail.index); const size_t new_entries = rep->entries(head.index, tail.index);
if (rep->refcount.IsMutable() && extra <= (rep->capacity() - new_entries)) { if (rep->refcount.IsOne() && extra <= (rep->capacity() - new_entries)) {
// We adopt a privately owned rep and no extra entries needed. // We adopt a privately owned rep and no extra entries needed.
if (head.index != rep->head_) UnrefEntries(rep, rep->head_, head.index); if (head.index != rep->head_) UnrefEntries(rep, rep->head_, head.index);
if (tail.index != rep->tail_) UnrefEntries(rep, tail.index, rep->tail_); if (tail.index != rep->tail_) UnrefEntries(rep, tail.index, rep->tail_);
...@@ -715,7 +715,7 @@ CordRepRing* CordRepRing::RemovePrefix(CordRepRing* rep, size_t len, ...@@ -715,7 +715,7 @@ CordRepRing* CordRepRing::RemovePrefix(CordRepRing* rep, size_t len,
} }
Position head = rep->Find(len); Position head = rep->Find(len);
if (rep->refcount.IsMutable()) { if (rep->refcount.IsOne()) {
if (head.index != rep->head_) UnrefEntries(rep, rep->head_, head.index); if (head.index != rep->head_) UnrefEntries(rep, rep->head_, head.index);
rep->head_ = head.index; rep->head_ = head.index;
} else { } else {
...@@ -745,7 +745,7 @@ CordRepRing* CordRepRing::RemoveSuffix(CordRepRing* rep, size_t len, ...@@ -745,7 +745,7 @@ CordRepRing* CordRepRing::RemoveSuffix(CordRepRing* rep, size_t len,
} }
Position tail = rep->FindTail(rep->length - len); Position tail = rep->FindTail(rep->length - len);
if (rep->refcount.IsMutable()) { if (rep->refcount.IsOne()) {
// We adopt a privately owned rep, scrub. // We adopt a privately owned rep, scrub.
if (tail.index != rep->tail_) UnrefEntries(rep, tail.index, rep->tail_); if (tail.index != rep->tail_) UnrefEntries(rep, tail.index, rep->tail_);
rep->tail_ = tail.index; rep->tail_ = tail.index;
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "absl/debugging/stacktrace.h" #include "absl/debugging/stacktrace.h"
#include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_btree.h" #include "absl/strings/internal/cord_rep_btree.h"
#include "absl/strings/internal/cord_rep_crc.h"
#include "absl/strings/internal/cord_rep_ring.h" #include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/internal/cordz_handle.h" #include "absl/strings/internal/cordz_handle.h"
#include "absl/strings/internal/cordz_statistics.h" #include "absl/strings/internal/cordz_statistics.h"
...@@ -81,6 +82,14 @@ class CordRepAnalyzer { ...@@ -81,6 +82,14 @@ class CordRepAnalyzer {
size_t refcount = rep->refcount.Get(); size_t refcount = rep->refcount.Get();
RepRef repref{rep, (refcount > 1) ? refcount - 1 : 1}; RepRef repref{rep, (refcount > 1) ? refcount - 1 : 1};
// Process the top level CRC node, if present.
if (repref.rep->tag == CRC) {
statistics_.node_count++;
statistics_.node_counts.crc++;
memory_usage_.Add(sizeof(CordRepCrc), repref.refcount);
repref = repref.Child(repref.rep->crc()->child);
}
// Process all top level linear nodes (substrings and flats). // Process all top level linear nodes (substrings and flats).
repref = CountLinearReps(repref, memory_usage_); repref = CountLinearReps(repref, memory_usage_);
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "absl/strings/cord.h" #include "absl/strings/cord.h"
#include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_btree.h" #include "absl/strings/internal/cord_rep_btree.h"
#include "absl/strings/internal/cord_rep_crc.h"
#include "absl/strings/internal/cord_rep_flat.h" #include "absl/strings/internal/cord_rep_flat.h"
#include "absl/strings/internal/cord_rep_ring.h" #include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/internal/cordz_info.h" #include "absl/strings/internal/cordz_info.h"
...@@ -535,6 +536,27 @@ TEST(CordzInfoStatisticsTest, BtreeNodeShared) { ...@@ -535,6 +536,27 @@ TEST(CordzInfoStatisticsTest, BtreeNodeShared) {
EXPECT_THAT(SampleCord(tree), EqStatistics(expected)); EXPECT_THAT(SampleCord(tree), EqStatistics(expected));
} }
TEST(CordzInfoStatisticsTest, Crc) {
RefHelper ref;
auto* left = Flat(1000);
auto* right = Flat(1000);
auto* concat = Concat(left, right);
auto* crc = ref.NeedsUnref(CordRepCrc::New(concat, 12345));
CordzStatistics expected;
expected.size = concat->length;
expected.estimated_memory_usage =
SizeOf(crc) + SizeOf(concat) + SizeOf(left) + SizeOf(right);
expected.estimated_fair_share_memory_usage = expected.estimated_memory_usage;
expected.node_count = 4;
expected.node_counts.flat = 2;
expected.node_counts.flat_1k = 2;
expected.node_counts.concat = 1;
expected.node_counts.crc = 1;
EXPECT_THAT(SampleCord(crc), EqStatistics(expected));
}
TEST(CordzInfoStatisticsTest, ThreadSafety) { TEST(CordzInfoStatisticsTest, ThreadSafety) {
Notification stop; Notification stop;
static constexpr int kNumThreads = 8; static constexpr int kNumThreads = 8;
......
...@@ -41,6 +41,7 @@ struct CordzStatistics { ...@@ -41,6 +41,7 @@ struct CordzStatistics {
size_t concat = 0; // #concat reps size_t concat = 0; // #concat reps
size_t ring = 0; // #ring buffer reps size_t ring = 0; // #ring buffer reps
size_t btree = 0; // #btree reps size_t btree = 0; // #btree reps
size_t crc = 0; // #crc reps
}; };
// The size of the cord in bytes. This matches the result of Cord::size(). // The size of the cord in bytes. This matches the result of Cord::size().
......
...@@ -60,6 +60,7 @@ class CordzUpdateTracker { ...@@ -60,6 +60,7 @@ class CordzUpdateTracker {
kPrependString, kPrependString,
kRemovePrefix, kRemovePrefix,
kRemoveSuffix, kRemoveSuffix,
kSetExpectedChecksum,
kSubCord, kSubCord,
// kNumMethods defines the number of entries: must be the last entry. // kNumMethods defines the number of entries: must be the last entry.
......
...@@ -58,6 +58,7 @@ Methods AllMethods() { ...@@ -58,6 +58,7 @@ Methods AllMethods() {
Method::kPrependString, Method::kPrependString,
Method::kRemovePrefix, Method::kRemovePrefix,
Method::kRemoveSuffix, Method::kRemoveSuffix,
Method::kSetExpectedChecksum,
Method::kSubCord}; Method::kSubCord};
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment