Commit efb035a5 by Martijn Vels Committed by Copybara-Service

Remove CordRepRing experiment.

We have no intention to use it instead of the CordRepBtree implementation, so cleanup up and remove all code and references.

PiperOrigin-RevId: 563803813
Change-Id: I95a67318d0f722f3eb7ecdcc7b6c87e28f2e26dd
parent 09d29c58
......@@ -288,9 +288,6 @@ set(ABSL_INTERNAL_DLL_FILES
"strings/internal/cord_rep_consume.h"
"strings/internal/cord_rep_consume.cc"
"strings/internal/cord_rep_flat.h"
"strings/internal/cord_rep_ring.cc"
"strings/internal/cord_rep_ring.h"
"strings/internal/cord_rep_ring_reader.h"
"strings/internal/cordz_functions.cc"
"strings/internal/cordz_functions.h"
"strings/internal/cordz_handle.cc"
......
......@@ -360,7 +360,6 @@ cc_library(
"internal/cord_rep_btree_reader.cc",
"internal/cord_rep_consume.cc",
"internal/cord_rep_crc.cc",
"internal/cord_rep_ring.cc",
],
hdrs = [
"internal/cord_data_edge.h",
......@@ -371,8 +370,6 @@ cc_library(
"internal/cord_rep_consume.h",
"internal/cord_rep_crc.h",
"internal/cord_rep_flat.h",
"internal/cord_rep_ring.h",
"internal/cord_rep_ring_reader.h",
],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
......@@ -889,41 +886,6 @@ cc_test(
)
cc_test(
name = "cord_ring_test",
size = "medium",
srcs = ["cord_ring_test.cc"],
copts = ABSL_TEST_COPTS,
visibility = ["//visibility:private"],
deps = [
":cord_internal",
":strings",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/base:raw_logging_internal",
"//absl/debugging:leak_check",
"//absl/types:span",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "cord_ring_reader_test",
size = "medium",
srcs = ["cord_ring_reader_test.cc"],
copts = ABSL_TEST_COPTS,
visibility = ["//visibility:private"],
deps = [
":cord_internal",
":strings",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/debugging:leak_check",
"//absl/types:span",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "substitute_test",
size = "small",
srcs = ["substitute_test.cc"],
......
......@@ -637,8 +637,6 @@ absl_cc_library(
"internal/cord_rep_crc.h"
"internal/cord_rep_consume.h"
"internal/cord_rep_flat.h"
"internal/cord_rep_ring.h"
"internal/cord_rep_ring_reader.h"
SRCS
"internal/cord_internal.cc"
"internal/cord_rep_btree.cc"
......@@ -646,7 +644,6 @@ absl_cc_library(
"internal/cord_rep_btree_reader.cc"
"internal/cord_rep_crc.cc"
"internal/cord_rep_consume.cc"
"internal/cord_rep_ring.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
......@@ -1117,41 +1114,6 @@ absl_cc_test(
absl_cc_test(
NAME
cord_ring_test
SRCS
"cord_ring_test.cc"
COPTS
${ABSL_TEST_COPTS}
DEPS
absl::base
absl::config
absl::cord_internal
absl::core_headers
absl::raw_logging_internal
absl::span
absl::strings
GTest::gmock_main
)
absl_cc_test(
NAME
cord_ring_reader_test
SRCS
"cord_ring_reader_test.cc"
COPTS
${ABSL_TEST_COPTS}
DEPS
absl::base
absl::config
absl::cord_internal
absl::core_headers
absl::span
absl::strings
GTest::gmock_main
)
absl_cc_test(
NAME
cordz_test
SRCS
"cordz_test.cc"
......
......@@ -86,7 +86,6 @@
#include "absl/strings/internal/cord_rep_btree.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/cordz_functions.h"
#include "absl/strings/internal/cordz_info.h"
#include "absl/strings/internal/cordz_statistics.h"
......
......@@ -24,7 +24,6 @@
#include "absl/strings/internal/cord_internal.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"
namespace absl {
ABSL_NAMESPACE_BEGIN
......@@ -131,16 +130,6 @@ void AnalyzeDataEdge(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) {
raw_usage.Add(size, rep);
}
// Computes the memory size of the provided Ring tree.
template <Mode mode>
void AnalyzeRing(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) {
const CordRepRing* ring = rep.rep->ring();
raw_usage.Add(CordRepRing::AllocSize(ring->capacity()), rep);
ring->ForEach([&](CordRepRing::index_type pos) {
AnalyzeDataEdge(rep.Child(ring->entry_child(pos)), raw_usage);
});
}
// Computes the memory size of the provided Btree tree.
template <Mode mode>
void AnalyzeBtree(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) {
......@@ -175,8 +164,6 @@ size_t GetEstimatedUsage(const CordRep* rep) {
AnalyzeDataEdge(repref, raw_usage);
} else if (repref.rep->tag == BTREE) {
AnalyzeBtree(repref, raw_usage);
} else if (repref.rep->tag == RING) {
AnalyzeRing(repref, raw_usage);
} else {
assert(false);
}
......
// Copyright 2020 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 <array>
#include <cstdlib>
#include <cstring>
#include <ctime>
#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_ring.h"
#include "absl/strings/internal/cord_rep_ring_reader.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
namespace {
using testing::Eq;
// Creates a flat for testing
CordRep* MakeFlat(absl::string_view s) {
CordRepFlat* flat = CordRepFlat::New(s.length());
memcpy(flat->Data(), s.data(), s.length());
flat->length = s.length();
return flat;
}
CordRepRing* FromFlats(Span<absl::string_view const> flats) {
CordRepRing* ring = CordRepRing::Create(MakeFlat(flats[0]), flats.size() - 1);
for (int i = 1; i < flats.size(); ++i) {
ring = CordRepRing::Append(ring, MakeFlat(flats[i]));
}
return ring;
}
std::array<absl::string_view, 12> TestFlats() {
return {"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
}
TEST(CordRingReaderTest, DefaultInstance) {
CordRepRingReader reader;
EXPECT_FALSE(static_cast<bool>(reader));
EXPECT_THAT(reader.ring(), Eq(nullptr));
#ifndef NDEBUG
EXPECT_DEATH_IF_SUPPORTED(reader.length(), ".*");
EXPECT_DEATH_IF_SUPPORTED(reader.consumed(), ".*");
EXPECT_DEATH_IF_SUPPORTED(reader.remaining(), ".*");
EXPECT_DEATH_IF_SUPPORTED(reader.Next(), ".*");
EXPECT_DEATH_IF_SUPPORTED(reader.Seek(0), ".*");
#endif
}
TEST(CordRingReaderTest, Reset) {
CordRepRingReader reader;
auto flats = TestFlats();
CordRepRing* ring = FromFlats(flats);
absl::string_view first = reader.Reset(ring);
EXPECT_THAT(first, Eq(flats[0]));
EXPECT_TRUE(static_cast<bool>(reader));
EXPECT_THAT(reader.ring(), Eq(ring));
EXPECT_THAT(reader.index(), Eq(ring->head()));
EXPECT_THAT(reader.node(), Eq(ring->entry_child(ring->head())));
EXPECT_THAT(reader.length(), Eq(ring->length));
EXPECT_THAT(reader.consumed(), Eq(flats[0].length()));
EXPECT_THAT(reader.remaining(), Eq(ring->length - reader.consumed()));
reader.Reset();
EXPECT_FALSE(static_cast<bool>(reader));
EXPECT_THAT(reader.ring(), Eq(nullptr));
CordRep::Unref(ring);
}
TEST(CordRingReaderTest, Next) {
CordRepRingReader reader;
auto flats = TestFlats();
CordRepRing* ring = FromFlats(flats);
CordRepRing::index_type head = ring->head();
reader.Reset(ring);
size_t consumed = reader.consumed();
size_t remaining = reader.remaining();
for (int i = 1; i < flats.size(); ++i) {
CordRepRing::index_type index = ring->advance(head, i);
consumed += flats[i].length();
remaining -= flats[i].length();
absl::string_view next = reader.Next();
ASSERT_THAT(next, Eq(flats[i]));
ASSERT_THAT(reader.index(), Eq(index));
ASSERT_THAT(reader.node(), Eq(ring->entry_child(index)));
ASSERT_THAT(reader.consumed(), Eq(consumed));
ASSERT_THAT(reader.remaining(), Eq(remaining));
}
#ifndef NDEBUG
EXPECT_DEATH_IF_SUPPORTED(reader.Next(), ".*");
#endif
CordRep::Unref(ring);
}
TEST(CordRingReaderTest, SeekForward) {
CordRepRingReader reader;
auto flats = TestFlats();
CordRepRing* ring = FromFlats(flats);
CordRepRing::index_type head = ring->head();
reader.Reset(ring);
size_t consumed = 0;
size_t remaining = ring->length;
for (int i = 0; i < flats.size(); ++i) {
CordRepRing::index_type index = ring->advance(head, i);
size_t offset = consumed;
consumed += flats[i].length();
remaining -= flats[i].length();
for (int off = 0; off < flats[i].length(); ++off) {
absl::string_view chunk = reader.Seek(offset + off);
ASSERT_THAT(chunk, Eq(flats[i].substr(off)));
ASSERT_THAT(reader.index(), Eq(index));
ASSERT_THAT(reader.node(), Eq(ring->entry_child(index)));
ASSERT_THAT(reader.consumed(), Eq(consumed));
ASSERT_THAT(reader.remaining(), Eq(remaining));
}
}
CordRep::Unref(ring);
}
TEST(CordRingReaderTest, SeekBackward) {
CordRepRingReader reader;
auto flats = TestFlats();
CordRepRing* ring = FromFlats(flats);
CordRepRing::index_type head = ring->head();
reader.Reset(ring);
size_t consumed = ring->length;
size_t remaining = 0;
for (int i = flats.size() - 1; i >= 0; --i) {
CordRepRing::index_type index = ring->advance(head, i);
size_t offset = consumed - flats[i].length();
for (int off = 0; off < flats[i].length(); ++off) {
absl::string_view chunk = reader.Seek(offset + off);
ASSERT_THAT(chunk, Eq(flats[i].substr(off)));
ASSERT_THAT(reader.index(), Eq(index));
ASSERT_THAT(reader.node(), Eq(ring->entry_child(index)));
ASSERT_THAT(reader.consumed(), Eq(consumed));
ASSERT_THAT(reader.remaining(), Eq(remaining));
}
consumed -= flats[i].length();
remaining += flats[i].length();
}
#ifndef NDEBUG
EXPECT_DEATH_IF_SUPPORTED(reader.Seek(ring->length), ".*");
#endif
CordRep::Unref(ring);
}
} // namespace
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
// Copyright 2020 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 <cassert>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <ostream>
#include <random>
#include <sstream>
#include <string>
#include <vector>
#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_flat.h"
#include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
extern thread_local bool cord_ring;
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace {
using RandomEngine = std::mt19937_64;
using ::absl::cord_internal::CordRep;
using ::absl::cord_internal::CordRepConcat;
using ::absl::cord_internal::CordRepExternal;
using ::absl::cord_internal::CordRepFlat;
using ::absl::cord_internal::CordRepRing;
using ::absl::cord_internal::CordRepSubstring;
using ::absl::cord_internal::EXTERNAL;
using ::absl::cord_internal::SUBSTRING;
using testing::ElementsAre;
using testing::ElementsAreArray;
using testing::Eq;
using testing::Ge;
using testing::Le;
using testing::Lt;
using testing::Ne;
using testing::SizeIs;
using index_type = CordRepRing::index_type;
enum InputShareMode { kPrivate, kShared, kSharedIndirect };
// TestParam class used by all test fixtures.
// Not all fixtures use all possible input combinations
struct TestParam {
TestParam() = default;
explicit TestParam(InputShareMode input_share_mode)
: input_share_mode(input_share_mode) {}
// Run the test with the 'rep under test' to be privately owned.
// Otherwise, the rep has a shared ref count of 2 or higher.
bool refcount_is_one = true;
// Run the test with the 'rep under test' being allocated with enough capacity
// to accommodate any modifications made to it. Otherwise, the rep has zero
// extra (reserve) capacity.
bool with_capacity = true;
// For test providing possibly shared input such as Append(.., CordpRep*),
// this field defines if that input is adopted with a refcount of one
// (privately owned / donated), or shared. For composite inputs such as
// 'substring of flat', we also have the 'shared indirect' value which means
// the top level node is not shared, but the contained child node is shared.
InputShareMode input_share_mode = kPrivate;
std::string ToString() const {
return absl::StrCat(refcount_is_one ? "Private" : "Shared",
with_capacity ? "" : "_NoCapacity",
(input_share_mode == kPrivate) ? ""
: (input_share_mode == kShared)
? "_SharedInput"
: "_IndirectSharedInput");
}
};
using TestParams = std::vector<TestParam>;
// Matcher validating when mutable copies are required / performed.
MATCHER_P2(EqIfPrivate, param, rep,
absl::StrCat("Equal 0x", absl::Hex(rep), " if private")) {
return param.refcount_is_one ? arg == rep : true;
}
// Matcher validating when mutable copies are required / performed.
MATCHER_P2(EqIfPrivateAndCapacity, param, rep,
absl::StrCat("Equal 0x", absl::Hex(rep),
" if private and capacity")) {
return (param.refcount_is_one && param.with_capacity) ? arg == rep : true;
}
// Matcher validating a shared ring was re-allocated. Should only be used for
// tests doing exactly one update as subsequent updates could return the
// original (freed and re-used) pointer.
MATCHER_P2(NeIfShared, param, rep,
absl::StrCat("Not equal 0x", absl::Hex(rep), " if shared")) {
return param.refcount_is_one ? true : arg != rep;
}
MATCHER_P2(EqIfInputPrivate, param, rep, "Equal if input is private") {
return param.input_share_mode == kPrivate ? arg == rep : arg != rep;
}
// Matcher validating the core in-variants of the CordRepRing instance.
MATCHER(IsValidRingBuffer, "RingBuffer is valid") {
std::stringstream ss;
if (!arg->IsValid(ss)) {
*result_listener << "\nERROR: " << ss.str() << "\nRING = " << *arg;
return false;
}
return true;
}
// Returns the flats contained in the provided CordRepRing
std::vector<string_view> ToFlats(const CordRepRing* r) {
std::vector<string_view> flats;
flats.reserve(r->entries());
index_type pos = r->head();
do {
flats.push_back(r->entry_data(pos));
} while ((pos = r->advance(pos)) != r->tail());
return flats;
}
class not_a_string_view {
public:
explicit not_a_string_view(absl::string_view s)
: data_(s.data()), size_(s.size()) {}
explicit not_a_string_view(const void* data, size_t size)
: data_(data), size_(size) {}
not_a_string_view remove_prefix(size_t n) const {
return not_a_string_view(static_cast<const char*>(data_) + n, size_ - n);
}
not_a_string_view remove_suffix(size_t n) const {
return not_a_string_view(data_, size_ - n);
}
const void* data() const { return data_; }
size_t size() const { return size_; }
private:
const void* data_;
size_t size_;
};
bool operator==(not_a_string_view lhs, not_a_string_view rhs) {
return lhs.data() == rhs.data() && lhs.size() == rhs.size();
}
std::ostream& operator<<(std::ostream& s, not_a_string_view rhs) {
return s << "{ data: " << rhs.data() << " size: " << rhs.size() << "}";
}
std::vector<not_a_string_view> ToRawFlats(const CordRepRing* r) {
std::vector<not_a_string_view> flats;
flats.reserve(r->entries());
index_type pos = r->head();
do {
flats.emplace_back(r->entry_data(pos));
} while ((pos = r->advance(pos)) != r->tail());
return flats;
}
// Returns the value contained in the provided CordRepRing
std::string ToString(const CordRepRing* r) {
std::string value;
value.reserve(r->length);
index_type pos = r->head();
do {
absl::string_view sv = r->entry_data(pos);
value.append(sv.data(), sv.size());
} while ((pos = r->advance(pos)) != r->tail());
return value;
}
// Creates a flat for testing
CordRep* MakeFlat(absl::string_view s, size_t extra = 0) {
CordRepFlat* flat = CordRepFlat::New(s.length() + extra);
memcpy(flat->Data(), s.data(), s.length());
flat->length = s.length();
return flat;
}
// Creates an external node for testing
CordRepExternal* MakeExternal(absl::string_view s) {
struct Rep : public CordRepExternal {
std::string s;
explicit Rep(absl::string_view s) : s(s) {
this->tag = EXTERNAL;
this->base = s.data();
this->length = s.length();
this->releaser_invoker = [](CordRepExternal* self) {
delete static_cast<Rep*>(self);
};
}
};
return new Rep(s);
}
CordRepExternal* MakeFakeExternal(size_t length) {
struct Rep : public CordRepExternal {
std::string s;
explicit Rep(size_t len) {
this->tag = EXTERNAL;
this->base = reinterpret_cast<const char*>(this->storage);
this->length = len;
this->releaser_invoker = [](CordRepExternal* self) {
delete static_cast<Rep*>(self);
};
}
};
return new Rep(length);
}
// Creates a flat or an external node for testing depending on the size.
CordRep* MakeLeaf(absl::string_view s, size_t extra = 0) {
if (s.size() <= absl::cord_internal::kMaxFlatLength) {
return MakeFlat(s, extra);
} else {
return MakeExternal(s);
}
}
// Creates a substring node
CordRepSubstring* MakeSubstring(size_t start, size_t len, CordRep* rep) {
auto* sub = new CordRepSubstring;
sub->tag = SUBSTRING;
sub->start = start;
sub->length = (len <= 0) ? rep->length - start + len : len;
sub->child = rep;
return sub;
}
// Creates a substring node removing the specified prefix
CordRepSubstring* RemovePrefix(size_t start, CordRep* rep) {
return MakeSubstring(start, rep->length - start, rep);
}
// Creates a substring node removing the specified suffix
CordRepSubstring* RemoveSuffix(size_t length, CordRep* rep) {
return MakeSubstring(0, rep->length - length, rep);
}
enum Composition { kMix, kAppend, kPrepend };
Composition RandomComposition() {
RandomEngine rng(GTEST_FLAG_GET(random_seed));
return (rng() & 1) ? kMix : ((rng() & 1) ? kAppend : kPrepend);
}
absl::string_view ToString(Composition composition) {
switch (composition) {
case kAppend:
return "Append";
case kPrepend:
return "Prepend";
case kMix:
return "Mix";
}
assert(false);
return "???";
}
constexpr const char* kFox = "The quick brown fox jumps over the lazy dog";
constexpr const char* kFoxFlats[] = {"The ", "quick ", "brown ",
"fox ", "jumps ", "over ",
"the ", "lazy ", "dog"};
CordRepRing* FromFlats(Span<const char* const> flats,
Composition composition = kAppend) {
if (flats.empty()) return nullptr;
CordRepRing* ring = nullptr;
switch (composition) {
case kAppend:
ring = CordRepRing::Create(MakeLeaf(flats.front()), flats.size() - 1);
for (int i = 1; i < flats.size(); ++i) {
ring = CordRepRing::Append(ring, MakeLeaf(flats[i]));
}
break;
case kPrepend:
ring = CordRepRing::Create(MakeLeaf(flats.back()), flats.size() - 1);
for (int i = static_cast<int>(flats.size() - 2); i >= 0; --i) {
ring = CordRepRing::Prepend(ring, MakeLeaf(flats[i]));
}
break;
case kMix:
size_t middle1 = flats.size() / 2, middle2 = middle1;
ring = CordRepRing::Create(MakeLeaf(flats[middle1]), flats.size() - 1);
if (!flats.empty()) {
if ((flats.size() & 1) == 0) {
ring = CordRepRing::Prepend(ring, MakeLeaf(flats[--middle1]));
}
for (int i = 1; i <= middle1; ++i) {
ring = CordRepRing::Prepend(ring, MakeLeaf(flats[middle1 - i]));
ring = CordRepRing::Append(ring, MakeLeaf(flats[middle2 + i]));
}
}
break;
}
EXPECT_THAT(ToFlats(ring), ElementsAreArray(flats));
return ring;
}
std::ostream& operator<<(std::ostream& s, const TestParam& param) {
return s << param.ToString();
}
std::string TestParamToString(const testing::TestParamInfo<TestParam>& info) {
return info.param.ToString();
}
class CordRingTest : public testing::Test {
public:
~CordRingTest() override {
for (CordRep* rep : unrefs_) {
CordRep::Unref(rep);
}
}
template <typename CordRepType>
CordRepType* NeedsUnref(CordRepType* rep) {
assert(rep);
unrefs_.push_back(rep);
return rep;
}
template <typename CordRepType>
CordRepType* Ref(CordRepType* rep) {
CordRep::Ref(rep);
return NeedsUnref(rep);
}
private:
std::vector<CordRep*> unrefs_;
};
class CordRingTestWithParam : public testing::TestWithParam<TestParam> {
public:
~CordRingTestWithParam() override {
for (CordRep* rep : unrefs_) {
CordRep::Unref(rep);
}
}
CordRepRing* CreateWithCapacity(CordRep* child, size_t extra_capacity) {
if (!GetParam().with_capacity) extra_capacity = 0;
CordRepRing* ring = CordRepRing::Create(child, extra_capacity);
ring->SetCapacityForTesting(1 + extra_capacity);
return RefIfShared(ring);
}
bool Shared() const { return !GetParam().refcount_is_one; }
bool InputShared() const { return GetParam().input_share_mode == kShared; }
bool InputSharedIndirect() const {
return GetParam().input_share_mode == kSharedIndirect;
}
template <typename CordRepType>
CordRepType* NeedsUnref(CordRepType* rep) {
assert(rep);
unrefs_.push_back(rep);
return rep;
}
template <typename CordRepType>
CordRepType* Ref(CordRepType* rep) {
CordRep::Ref(rep);
return NeedsUnref(rep);
}
template <typename CordRepType>
CordRepType* RefIfShared(CordRepType* rep) {
return Shared() ? Ref(rep) : rep;
}
template <typename CordRepType>
CordRepType* RefIfInputShared(CordRepType* rep) {
return InputShared() ? Ref(rep) : rep;
}
template <typename CordRepType>
CordRepType* RefIfInputSharedIndirect(CordRepType* rep) {
return InputSharedIndirect() ? Ref(rep) : rep;
}
private:
std::vector<CordRep*> unrefs_;
};
class CordRingCreateTest : public CordRingTestWithParam {
public:
static TestParams CreateTestParams() {
TestParams params;
params.emplace_back(InputShareMode::kPrivate);
params.emplace_back(InputShareMode::kShared);
return params;
}
};
class CordRingSubTest : public CordRingTestWithParam {
public:
static TestParams CreateTestParams() {
TestParams params;
for (bool refcount_is_one : {true, false}) {
TestParam param;
param.refcount_is_one = refcount_is_one;
params.push_back(param);
}
return params;
}
};
class CordRingBuildTest : public CordRingTestWithParam {
public:
static TestParams CreateTestParams() {
TestParams params;
for (bool refcount_is_one : {true, false}) {
for (bool with_capacity : {true, false}) {
TestParam param;
param.refcount_is_one = refcount_is_one;
param.with_capacity = with_capacity;
params.push_back(param);
}
}
return params;
}
};
class CordRingCreateFromTreeTest : public CordRingTestWithParam {
public:
static TestParams CreateTestParams() {
TestParams params;
params.emplace_back(InputShareMode::kPrivate);
params.emplace_back(InputShareMode::kShared);
params.emplace_back(InputShareMode::kSharedIndirect);
return params;
}
};
class CordRingBuildInputTest : public CordRingTestWithParam {
public:
static TestParams CreateTestParams() {
TestParams params;
for (bool refcount_is_one : {true, false}) {
for (bool with_capacity : {true, false}) {
for (InputShareMode share_mode : {kPrivate, kShared, kSharedIndirect}) {
TestParam param;
param.refcount_is_one = refcount_is_one;
param.with_capacity = with_capacity;
param.input_share_mode = share_mode;
params.push_back(param);
}
}
}
return params;
}
};
INSTANTIATE_TEST_SUITE_P(WithParam, CordRingSubTest,
testing::ValuesIn(CordRingSubTest::CreateTestParams()),
TestParamToString);
INSTANTIATE_TEST_SUITE_P(
WithParam, CordRingCreateTest,
testing::ValuesIn(CordRingCreateTest::CreateTestParams()),
TestParamToString);
INSTANTIATE_TEST_SUITE_P(
WithParam, CordRingCreateFromTreeTest,
testing::ValuesIn(CordRingCreateFromTreeTest::CreateTestParams()),
TestParamToString);
INSTANTIATE_TEST_SUITE_P(
WithParam, CordRingBuildTest,
testing::ValuesIn(CordRingBuildTest::CreateTestParams()),
TestParamToString);
INSTANTIATE_TEST_SUITE_P(
WithParam, CordRingBuildInputTest,
testing::ValuesIn(CordRingBuildInputTest::CreateTestParams()),
TestParamToString);
TEST_P(CordRingCreateTest, CreateFromFlat) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
CordRepRing* result = NeedsUnref(CordRepRing::Create(MakeFlat(str1)));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str1.size()));
EXPECT_THAT(ToFlats(result), ElementsAre(str1));
}
TEST_P(CordRingCreateTest, CreateFromRing) {
CordRepRing* ring = RefIfShared(FromFlats(kFoxFlats));
CordRepRing* result = NeedsUnref(CordRepRing::Create(ring));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats));
}
TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringRing) {
CordRepRing* ring = RefIfInputSharedIndirect(FromFlats(kFoxFlats));
CordRep* sub = RefIfInputShared(MakeSubstring(2, 11, ring));
CordRepRing* result = NeedsUnref(CordRepRing::Create(sub));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfInputPrivate(GetParam(), ring));
EXPECT_THAT(ToString(result), string_view(kFox).substr(2, 11));
}
TEST_F(CordRingTest, CreateWithIllegalExtraCapacity) {
#if defined(ABSL_HAVE_EXCEPTIONS)
CordRep* flat = NeedsUnref(MakeFlat("Hello world"));
try {
CordRepRing::Create(flat, CordRepRing::kMaxCapacity);
GTEST_FAIL() << "expected std::length_error exception";
} catch (const std::length_error&) {
}
#elif defined(GTEST_HAS_DEATH_TEST)
CordRep* flat = NeedsUnref(MakeFlat("Hello world"));
EXPECT_DEATH(CordRepRing::Create(flat, CordRepRing::kMaxCapacity), ".*");
#endif
}
TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfFlat) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
auto* flat = RefIfInputShared(MakeFlat(str1));
auto* child = RefIfInputSharedIndirect(MakeSubstring(4, 20, flat));
CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(20));
EXPECT_THAT(ToFlats(result), ElementsAre(str1.substr(4, 20)));
}
TEST_P(CordRingCreateTest, CreateFromExternal) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
auto* child = RefIfInputShared(MakeExternal(str1));
CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str1.size()));
EXPECT_THAT(ToFlats(result), ElementsAre(str1));
}
TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfExternal) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
auto* external = RefIfInputShared(MakeExternal(str1));
auto* child = RefIfInputSharedIndirect(MakeSubstring(1, 24, external));
CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(24));
EXPECT_THAT(ToFlats(result), ElementsAre(str1.substr(1, 24)));
}
TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfLargeExternal) {
auto* external = RefIfInputShared(MakeFakeExternal(1 << 20));
auto str = not_a_string_view(external->base, 1 << 20)
.remove_prefix(1 << 19)
.remove_suffix(6);
auto* child =
RefIfInputSharedIndirect(MakeSubstring(1 << 19, (1 << 19) - 6, external));
CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str.size()));
EXPECT_THAT(ToRawFlats(result), ElementsAre(str));
}
TEST_P(CordRingCreateTest, Properties) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
CordRepRing* result = NeedsUnref(CordRepRing::Create(MakeFlat(str1), 120));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->head(), Eq(0));
EXPECT_THAT(result->tail(), Eq(1));
EXPECT_THAT(result->capacity(), Ge(120 + 1));
EXPECT_THAT(result->capacity(), Le(2 * 120 + 1));
EXPECT_THAT(result->entries(), Eq(1));
EXPECT_THAT(result->begin_pos(), Eq(0));
}
TEST_P(CordRingCreateTest, EntryForNewFlat) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
CordRep* child = MakeFlat(str1);
CordRepRing* result = NeedsUnref(CordRepRing::Create(child, 120));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->entry_child(0), Eq(child));
EXPECT_THAT(result->entry_end_pos(0), Eq(str1.length()));
EXPECT_THAT(result->entry_data_offset(0), Eq(0));
}
TEST_P(CordRingCreateTest, EntryForNewFlatSubstring) {
absl::string_view str1 = "1234567890abcdefghijklmnopqrstuvwxyz";
CordRep* child = MakeFlat(str1);
CordRep* substring = MakeSubstring(10, 26, child);
CordRepRing* result = NeedsUnref(CordRepRing::Create(substring, 1));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->entry_child(0), Eq(child));
EXPECT_THAT(result->entry_end_pos(0), Eq(26));
EXPECT_THAT(result->entry_data_offset(0), Eq(10));
}
TEST_P(CordRingBuildTest, AppendFlat) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, MakeFlat(str2)));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2));
}
TEST_P(CordRingBuildTest, PrependFlat) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, MakeFlat(str2)));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1));
}
TEST_P(CordRingBuildTest, AppendString) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2));
}
TEST_P(CordRingBuildTest, AppendStringHavingExtra) {
absl::string_view str1 = "1234";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRepRing* ring = CreateWithCapacity(MakeFlat(str1, 26), 0);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
}
TEST_P(CordRingBuildTest, AppendStringHavingPartialExtra) {
absl::string_view str1 = "1234";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// Create flat with at least one extra byte. We don't expect to have sized
// alloc and capacity rounding to grant us enough to not make it partial.
auto* flat = MakeFlat(str1, 1);
size_t avail = flat->flat()->Capacity() - flat->length;
ASSERT_THAT(avail, Lt(str2.size())) << " adjust test for larger flats!";
// Construct the flats we do expect using all of `avail`.
absl::string_view str1a = str2.substr(0, avail);
absl::string_view str2a = str2.substr(avail);
CordRepRing* ring = CreateWithCapacity(flat, 1);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
if (GetParam().refcount_is_one) {
EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str1, str1a), str2a));
} else {
EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2));
}
}
TEST_P(CordRingBuildTest, AppendStringHavingExtraInSubstring) {
absl::string_view str1 = "123456789_1234";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRep* flat = RemovePrefix(10, MakeFlat(str1, 26));
CordRepRing* ring = CreateWithCapacity(flat, 0);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(result->length, Eq(4 + str2.size()));
if (GetParam().refcount_is_one) {
EXPECT_THAT(ToFlats(result), ElementsAre(StrCat("1234", str2)));
} else {
EXPECT_THAT(ToFlats(result), ElementsAre("1234", str2));
}
}
TEST_P(CordRingBuildTest, AppendStringHavingSharedExtra) {
absl::string_view str1 = "123456789_1234";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (int shared_type = 0; shared_type < 2; ++shared_type) {
SCOPED_TRACE(absl::StrCat("Shared extra type ", shared_type));
// Create a flat that is shared in some way.
CordRep* flat = nullptr;
CordRep* flat1 = nullptr;
if (shared_type == 0) {
// Shared flat
flat = CordRep::Ref(MakeFlat(str1.substr(10), 100));
} else if (shared_type == 1) {
// Shared flat inside private substring
flat1 = CordRep::Ref(MakeFlat(str1));
flat = RemovePrefix(10, flat1);
} else {
// Private flat inside shared substring
flat = CordRep::Ref(RemovePrefix(10, MakeFlat(str1, 100)));
}
CordRepRing* ring = CreateWithCapacity(flat, 1);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(result->length, Eq(4 + str2.size()));
EXPECT_THAT(ToFlats(result), ElementsAre("1234", str2));
CordRep::Unref(shared_type == 1 ? flat1 : flat);
}
}
TEST_P(CordRingBuildTest, AppendStringWithExtra) {
absl::string_view str1 = "1234";
absl::string_view str2 = "1234567890";
absl::string_view str3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2, 26));
result = CordRepRing::Append(result, str3);
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str1.size() + str2.size() + str3.size()));
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre(str1, StrCat(str2, str3)));
}
TEST_P(CordRingBuildTest, PrependString) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// Use external rep to avoid appending to first flat
CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
if (GetParam().with_capacity && GetParam().refcount_is_one) {
EXPECT_THAT(result, Eq(ring));
} else {
EXPECT_THAT(result, Ne(ring));
}
EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1));
}
TEST_P(CordRingBuildTest, PrependStringHavingExtra) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz1234";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRep* flat = RemovePrefix(26, MakeFlat(str1));
CordRepRing* ring = CreateWithCapacity(flat, 0);
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(result->length, Eq(4 + str2.size()));
if (GetParam().refcount_is_one) {
EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str2, "1234")));
} else {
EXPECT_THAT(ToFlats(result), ElementsAre(str2, "1234"));
}
}
TEST_P(CordRingBuildTest, PrependStringHavingSharedExtra) {
absl::string_view str1 = "123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
absl::string_view str2 = "abcdefghij";
absl::string_view str1a = str1.substr(10);
for (int shared_type = 1; shared_type < 2; ++shared_type) {
SCOPED_TRACE(absl::StrCat("Shared extra type ", shared_type));
// Create a flat that is shared in some way.
CordRep* flat = nullptr;
CordRep* flat1 = nullptr;
if (shared_type == 1) {
// Shared flat inside private substring
flat = RemovePrefix(10, flat1 = CordRep::Ref(MakeFlat(str1)));
} else {
// Private flat inside shared substring
flat = CordRep::Ref(RemovePrefix(10, MakeFlat(str1, 100)));
}
CordRepRing* ring = CreateWithCapacity(flat, 1);
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str1a.size() + str2.size()));
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1a));
CordRep::Unref(shared_type == 1 ? flat1 : flat);
}
}
TEST_P(CordRingBuildTest, PrependStringWithExtra) {
absl::string_view str1 = "1234";
absl::string_view str2 = "1234567890";
absl::string_view str3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2, 26));
ASSERT_THAT(result, IsValidRingBuffer());
result = CordRepRing::Prepend(result, str3);
EXPECT_THAT(result->length, Eq(str1.size() + str2.size() + str3.size()));
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str3, str2), str1));
}
TEST_P(CordRingBuildTest, AppendPrependStringMix) {
const auto& flats = kFoxFlats;
CordRepRing* ring = CreateWithCapacity(MakeFlat(flats[4]), 8);
CordRepRing* result = ring;
for (int i = 1; i <= 4; ++i) {
result = CordRepRing::Prepend(result, flats[4 - i]);
result = CordRepRing::Append(result, flats[4 + i]);
}
NeedsUnref(result);
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToString(result), kFox);
}
TEST_P(CordRingBuildTest, AppendPrependStringMixWithExtra) {
const auto& flats = kFoxFlats;
CordRepRing* ring = CreateWithCapacity(MakeFlat(flats[4], 100), 8);
CordRepRing* result = ring;
for (int i = 1; i <= 4; ++i) {
result = CordRepRing::Prepend(result, flats[4 - i], 100);
result = CordRepRing::Append(result, flats[4 + i], 100);
}
NeedsUnref(result);
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
if (GetParam().refcount_is_one) {
EXPECT_THAT(ToFlats(result),
ElementsAre("The quick brown fox ", "jumps over the lazy dog"));
} else {
EXPECT_THAT(ToFlats(result), ElementsAre("The quick brown fox ", "jumps ",
"over the lazy dog"));
}
}
TEST_P(CordRingBuildTest, AppendPrependStringMixWithPrependedExtra) {
const auto& flats = kFoxFlats;
CordRep* flat = MakeFlat(StrCat(std::string(50, '.'), flats[4]), 50);
CordRepRing* ring = CreateWithCapacity(RemovePrefix(50, flat), 0);
CordRepRing* result = ring;
for (int i = 1; i <= 4; ++i) {
result = CordRepRing::Prepend(result, flats[4 - i], 100);
result = CordRepRing::Append(result, flats[4 + i], 100);
}
result = NeedsUnref(result);
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
if (GetParam().refcount_is_one) {
EXPECT_THAT(ToFlats(result), ElementsAre(kFox));
} else {
EXPECT_THAT(ToFlats(result), ElementsAre("The quick brown fox ", "jumps ",
"over the lazy dog"));
}
}
TEST_P(CordRingSubTest, SubRing) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
string_view all = kFox;
for (size_t offset = 0; offset < all.size() - 1; ++offset) {
CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
CordRepRing* result = CordRepRing::SubRing(ring, offset, 0);
EXPECT_THAT(result, nullptr);
for (size_t len = 1; len < all.size() - offset; ++len) {
ring = RefIfShared(FromFlats(flats, composition));
result = NeedsUnref(CordRepRing::SubRing(ring, offset, len));
ASSERT_THAT(result, IsValidRingBuffer());
ASSERT_THAT(result, EqIfPrivate(GetParam(), ring));
ASSERT_THAT(result, NeIfShared(GetParam(), ring));
ASSERT_THAT(ToString(result), Eq(all.substr(offset, len)));
}
}
}
TEST_P(CordRingSubTest, SubRingFromLargeExternal) {
auto composition = RandomComposition();
std::string large_string(1 << 20, '.');
const char* flats[] = {
"abcdefghijklmnopqrstuvwxyz",
large_string.c_str(),
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
};
std::string buffer = absl::StrCat(flats[0], flats[1], flats[2]);
absl::string_view all = buffer;
for (size_t offset = 0; offset < 30; ++offset) {
CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
CordRepRing* result = CordRepRing::SubRing(ring, offset, 0);
EXPECT_THAT(result, nullptr);
for (size_t len = all.size() - 30; len < all.size() - offset; ++len) {
ring = RefIfShared(FromFlats(flats, composition));
result = NeedsUnref(CordRepRing::SubRing(ring, offset, len));
ASSERT_THAT(result, IsValidRingBuffer());
ASSERT_THAT(result, EqIfPrivate(GetParam(), ring));
ASSERT_THAT(result, NeIfShared(GetParam(), ring));
auto str = ToString(result);
ASSERT_THAT(str, SizeIs(len));
ASSERT_THAT(str, Eq(all.substr(offset, len)));
}
}
}
TEST_P(CordRingSubTest, RemovePrefix) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
string_view all = kFox;
CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
CordRepRing* result = CordRepRing::RemovePrefix(ring, all.size());
EXPECT_THAT(result, nullptr);
for (size_t len = 1; len < all.size(); ++len) {
ring = RefIfShared(FromFlats(flats, composition));
result = NeedsUnref(CordRepRing::RemovePrefix(ring, len));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
ASSERT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToString(result), Eq(all.substr(len)));
}
}
TEST_P(CordRingSubTest, RemovePrefixFromLargeExternal) {
CordRepExternal* external1 = MakeFakeExternal(1 << 20);
CordRepExternal* external2 = MakeFakeExternal(1 << 20);
CordRepRing* ring = CordRepRing::Create(external1, 1);
ring = CordRepRing::Append(ring, external2);
CordRepRing* result = NeedsUnref(CordRepRing::RemovePrefix(ring, 1 << 16));
EXPECT_THAT(
ToRawFlats(result),
ElementsAre(
not_a_string_view(external1->base, 1 << 20).remove_prefix(1 << 16),
not_a_string_view(external2->base, 1 << 20)));
}
TEST_P(CordRingSubTest, RemoveSuffix) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
string_view all = kFox;
CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
CordRepRing* result = CordRepRing::RemoveSuffix(ring, all.size());
EXPECT_THAT(result, nullptr);
for (size_t len = 1; len < all.size(); ++len) {
ring = RefIfShared(FromFlats(flats, composition));
result = NeedsUnref(CordRepRing::RemoveSuffix(ring, len));
ASSERT_THAT(result, IsValidRingBuffer());
ASSERT_THAT(result, EqIfPrivate(GetParam(), ring));
ASSERT_THAT(result, NeIfShared(GetParam(), ring));
ASSERT_THAT(ToString(result), Eq(all.substr(0, all.size() - len)));
}
}
TEST_P(CordRingSubTest, AppendRing) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats).subspan(1);
CordRepRing* ring = CreateWithCapacity(MakeFlat(kFoxFlats[0]), flats.size());
CordRepRing* child = FromFlats(flats, composition);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, child));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats));
}
TEST_P(CordRingBuildInputTest, AppendRingWithFlatOffset) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = RemovePrefix(10, child);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("Head", "brown ", "fox ", "jumps ",
"over ", "the ", "lazy ", "dog"));
}
TEST_P(CordRingBuildInputTest, AppendRingWithBrokenOffset) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = RemovePrefix(21, child);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result),
ElementsAre("Head", "umps ", "over ", "the ", "lazy ", "dog"));
}
TEST_P(CordRingBuildInputTest, AppendRingWithFlatLength) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = RemoveSuffix(8, child);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("Head", "The ", "quick ", "brown ",
"fox ", "jumps ", "over ", "the "));
}
TEST_P(CordRingBuildTest, AppendRingWithBrokenFlatLength) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = RemoveSuffix(15, child);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("Head", "The ", "quick ", "brown ",
"fox ", "jumps ", "ov"));
}
TEST_P(CordRingBuildTest, AppendRingMiddlePiece) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = MakeSubstring(7, child->length - 27, child);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result),
ElementsAre("Head", "ck ", "brown ", "fox ", "jum"));
}
TEST_P(CordRingBuildTest, AppendRingSinglePiece) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = RefIfInputShared(MakeSubstring(11, 3, child));
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("Head", "row"));
}
TEST_P(CordRingBuildInputTest, AppendRingSinglePieceWithPrefix) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
size_t extra_capacity = 1 + (GetParam().with_capacity ? flats.size() : 0);
CordRepRing* ring = CordRepRing::Create(MakeFlat("Head"), extra_capacity);
ring->SetCapacityForTesting(1 + extra_capacity);
ring = RefIfShared(CordRepRing::Prepend(ring, MakeFlat("Prepend")));
assert(ring->IsValid(std::cout));
CordRepRing* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = RefIfInputShared(MakeSubstring(11, 3, child));
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("Prepend", "Head", "row"));
}
TEST_P(CordRingBuildInputTest, PrependRing) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto fox = MakeSpan(kFoxFlats);
auto flats = MakeSpan(fox).subspan(0, fox.size() - 1);
CordRepRing* ring = CreateWithCapacity(MakeFlat(fox.back()), flats.size());
CordRepRing* child = RefIfInputShared(FromFlats(flats, composition));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, child));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats));
}
TEST_P(CordRingBuildInputTest, PrependRingWithFlatOffset) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped = RefIfInputSharedIndirect(RemovePrefix(10, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("brown ", "fox ", "jumps ", "over ",
"the ", "lazy ", "dog", "Tail"));
}
TEST_P(CordRingBuildInputTest, PrependRingWithBrokenOffset) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped = RefIfInputSharedIndirect(RemovePrefix(21, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result),
ElementsAre("umps ", "over ", "the ", "lazy ", "dog", "Tail"));
}
TEST_P(CordRingBuildInputTest, PrependRingWithFlatLength) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped = RefIfInputSharedIndirect(RemoveSuffix(8, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("The ", "quick ", "brown ", "fox ",
"jumps ", "over ", "the ", "Tail"));
}
TEST_P(CordRingBuildInputTest, PrependRingWithBrokenFlatLength) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped = RefIfInputSharedIndirect(RemoveSuffix(15, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("The ", "quick ", "brown ", "fox ",
"jumps ", "ov", "Tail"));
}
TEST_P(CordRingBuildInputTest, PrependRingMiddlePiece) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped =
RefIfInputSharedIndirect(MakeSubstring(7, child->length - 27, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result),
ElementsAre("ck ", "brown ", "fox ", "jum", "Tail"));
}
TEST_P(CordRingBuildInputTest, PrependRingSinglePiece) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped = RefIfInputSharedIndirect(MakeSubstring(11, 3, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("row", "Tail"));
}
TEST_P(CordRingBuildInputTest, PrependRingSinglePieceWithPrefix) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
size_t extra_capacity = 1 + (GetParam().with_capacity ? flats.size() : 0);
CordRepRing* ring = CordRepRing::Create(MakeFlat("Tail"), extra_capacity);
ring->SetCapacityForTesting(1 + extra_capacity);
ring = RefIfShared(CordRepRing::Prepend(ring, MakeFlat("Prepend")));
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped = RefIfInputSharedIndirect(MakeSubstring(11, 3, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result, NeIfShared(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("row", "Prepend", "Tail"));
}
TEST_F(CordRingTest, Find) {
constexpr const char* flats[] = {
"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
std::string value = ToString(ring);
for (int i = 0; i < value.length(); ++i) {
CordRepRing::Position found = ring->Find(i);
auto data = ring->entry_data(found.index);
ASSERT_THAT(found.offset, Lt(data.length()));
ASSERT_THAT(data[found.offset], Eq(value[i]));
}
}
TEST_F(CordRingTest, FindWithHint) {
constexpr const char* flats[] = {
"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
std::string value = ToString(ring);
#if defined(GTEST_HAS_DEATH_TEST)
// Test hint beyond valid position
index_type head = ring->head();
EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head), 0), ".*");
EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head), 9), ".*");
EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head, 3), 24), ".*");
#endif
int flat_pos = 0;
size_t flat_offset = 0;
for (auto sflat : flats) {
string_view flat(sflat);
for (int offset = 0; offset < flat.length(); ++offset) {
for (int start = 0; start <= flat_pos; ++start) {
index_type hint = ring->advance(ring->head(), start);
CordRepRing::Position found = ring->Find(hint, flat_offset + offset);
ASSERT_THAT(found.index, Eq(ring->advance(ring->head(), flat_pos)));
ASSERT_THAT(found.offset, Eq(offset));
}
}
++flat_pos;
flat_offset += flat.length();
}
}
TEST_F(CordRingTest, FindInLargeRing) {
constexpr const char* flats[] = {
"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
CordRepRing* ring = FromFlats(flats, composition);
for (int i = 0; i < 13; ++i) {
ring = CordRepRing::Append(ring, FromFlats(flats, composition));
}
NeedsUnref(ring);
std::string value = ToString(ring);
for (int i = 0; i < value.length(); ++i) {
CordRepRing::Position pos = ring->Find(i);
auto data = ring->entry_data(pos.index);
ASSERT_THAT(pos.offset, Lt(data.length()));
ASSERT_THAT(data[pos.offset], Eq(value[i]));
}
}
TEST_F(CordRingTest, FindTail) {
constexpr const char* flats[] = {
"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
std::string value = ToString(ring);
for (int i = 0; i < value.length(); ++i) {
CordRepRing::Position pos = ring->FindTail(i + 1);
auto data = ring->entry_data(ring->retreat(pos.index));
ASSERT_THAT(pos.offset, Lt(data.length()));
ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i]));
}
}
TEST_F(CordRingTest, FindTailWithHint) {
constexpr const char* flats[] = {
"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
std::string value = ToString(ring);
// Test hint beyond valid position
#if defined(GTEST_HAS_DEATH_TEST)
index_type head = ring->head();
EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head), 1), ".*");
EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head), 10), ".*");
EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head, 3), 26), ".*");
#endif
for (int i = 0; i < value.length(); ++i) {
CordRepRing::Position pos = ring->FindTail(i + 1);
auto data = ring->entry_data(ring->retreat(pos.index));
ASSERT_THAT(pos.offset, Lt(data.length()));
ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i]));
}
}
TEST_F(CordRingTest, FindTailInLargeRing) {
constexpr const char* flats[] = {
"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
CordRepRing* ring = FromFlats(flats, composition);
for (int i = 0; i < 13; ++i) {
ring = CordRepRing::Append(ring, FromFlats(flats, composition));
}
NeedsUnref(ring);
std::string value = ToString(ring);
for (int i = 0; i < value.length(); ++i) {
CordRepRing::Position pos = ring->FindTail(i + 1);
auto data = ring->entry_data(ring->retreat(pos.index));
ASSERT_THAT(pos.offset, Lt(data.length()));
ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i]));
}
}
TEST_F(CordRingTest, GetCharacter) {
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CordRepRing::Create(MakeFlat("Tail"), flats.size());
CordRep* child = FromFlats(flats, kAppend);
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, child));
std::string value = ToString(result);
for (int i = 0; i < value.length(); ++i) {
ASSERT_THAT(result->GetCharacter(i), Eq(value[i]));
}
}
TEST_F(CordRingTest, GetCharacterWithSubstring) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
auto* child = MakeSubstring(4, 20, MakeFlat(str1));
CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
ASSERT_THAT(result, IsValidRingBuffer());
std::string value = ToString(result);
for (int i = 0; i < value.length(); ++i) {
ASSERT_THAT(result->GetCharacter(i), Eq(value[i]));
}
}
TEST_F(CordRingTest, IsFlatSingleFlat) {
for (bool external : {false, true}) {
SCOPED_TRACE(external ? "With External" : "With Flat");
absl::string_view str = "Hello world";
CordRep* rep = external ? MakeExternal(str) : MakeFlat(str);
CordRepRing* ring = NeedsUnref(CordRepRing::Create(rep));
// The ring is a single non-fragmented flat:
absl::string_view fragment;
EXPECT_TRUE(ring->IsFlat(nullptr));
EXPECT_TRUE(ring->IsFlat(&fragment));
EXPECT_THAT(fragment, Eq("Hello world"));
fragment = "";
EXPECT_TRUE(ring->IsFlat(0, 11, nullptr));
EXPECT_TRUE(ring->IsFlat(0, 11, &fragment));
EXPECT_THAT(fragment, Eq("Hello world"));
// Arbitrary ranges must check true as well.
EXPECT_TRUE(ring->IsFlat(1, 4, &fragment));
EXPECT_THAT(fragment, Eq("ello"));
EXPECT_TRUE(ring->IsFlat(6, 5, &fragment));
EXPECT_THAT(fragment, Eq("world"));
}
}
TEST_F(CordRingTest, IsFlatMultiFlat) {
for (bool external : {false, true}) {
SCOPED_TRACE(external ? "With External" : "With Flat");
absl::string_view str1 = "Hello world";
absl::string_view str2 = "Halt and catch fire";
CordRep* rep1 = external ? MakeExternal(str1) : MakeFlat(str1);
CordRep* rep2 = external ? MakeExternal(str2) : MakeFlat(str2);
CordRepRing* ring = CordRepRing::Append(CordRepRing::Create(rep1), rep2);
NeedsUnref(ring);
// The ring is fragmented, IsFlat() on the entire cord must be false.
EXPECT_FALSE(ring->IsFlat(nullptr));
absl::string_view fragment = "Don't touch this";
EXPECT_FALSE(ring->IsFlat(&fragment));
EXPECT_THAT(fragment, Eq("Don't touch this"));
// Check for ranges exactly within both flats.
EXPECT_TRUE(ring->IsFlat(0, 11, &fragment));
EXPECT_THAT(fragment, Eq("Hello world"));
EXPECT_TRUE(ring->IsFlat(11, 19, &fragment));
EXPECT_THAT(fragment, Eq("Halt and catch fire"));
// Check for arbitrary partial range inside each flat.
EXPECT_TRUE(ring->IsFlat(1, 4, &fragment));
EXPECT_THAT(fragment, "ello");
EXPECT_TRUE(ring->IsFlat(26, 4, &fragment));
EXPECT_THAT(fragment, "fire");
// Check ranges spanning across both flats
fragment = "Don't touch this";
EXPECT_FALSE(ring->IsFlat(1, 18, &fragment));
EXPECT_FALSE(ring->IsFlat(10, 2, &fragment));
EXPECT_THAT(fragment, Eq("Don't touch this"));
}
}
TEST_F(CordRingTest, Dump) {
std::stringstream ss;
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = NeedsUnref(FromFlats(flats, kPrepend));
ss << *ring;
}
} // namespace
ABSL_NAMESPACE_END
} // namespace absl
......@@ -2110,8 +2110,6 @@ TEST_P(CordTest, DiabolicalGrowth) {
// This test exercises a diabolical Append(<one char>) on a cord, making the
// cord shared before each Append call resulting in a terribly fragmented
// resulting cord.
// TODO(b/183983616): Apply some minimum compaction when copying a shared
// source cord into a mutable copy for updates in CordRepRing.
RandomEngine rng(GTEST_FLAG_GET(random_seed));
const std::string expected = RandomLowercaseString(&rng, 5000);
absl::Cord cord;
......
......@@ -22,15 +22,12 @@
#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_ring.h"
#include "absl/strings/str_cat.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
ABSL_CONST_INIT std::atomic<bool> cord_ring_buffer_enabled(
kCordEnableRingBufferDefault);
ABSL_CONST_INIT std::atomic<bool> shallow_subcords_enabled(
kCordShallowSubcordsDefault);
......@@ -47,9 +44,6 @@ void CordRep::Destroy(CordRep* rep) {
if (rep->tag == BTREE) {
CordRepBtree::Destroy(rep->btree());
return;
} else if (rep->tag == RING) {
CordRepRing::Destroy(rep->ring());
return;
} else if (rep->tag == EXTERNAL) {
CordRepExternal::Delete(rep);
return;
......
......@@ -55,24 +55,15 @@ struct CordRepExternal;
struct CordRepFlat;
struct CordRepSubstring;
struct CordRepCrc;
class CordRepRing;
class CordRepBtree;
class CordzInfo;
// Default feature enable states for cord ring buffers
enum CordFeatureDefaults {
kCordEnableRingBufferDefault = false,
kCordShallowSubcordsDefault = false
};
enum CordFeatureDefaults { kCordShallowSubcordsDefault = false };
extern std::atomic<bool> cord_ring_buffer_enabled;
extern std::atomic<bool> shallow_subcords_enabled;
inline void enable_cord_ring_buffer(bool enable) {
cord_ring_buffer_enabled.store(enable, std::memory_order_relaxed);
}
inline void enable_shallow_subcords(bool enable) {
shallow_subcords_enabled.store(enable, std::memory_order_relaxed);
}
......@@ -215,7 +206,7 @@ enum CordRepKind {
SUBSTRING = 1,
CRC = 2,
BTREE = 3,
RING = 4,
UNUSED_4 = 4,
EXTERNAL = 5,
// We have different tags for different sized flat arrays,
......@@ -234,12 +225,8 @@ enum CordRepKind {
// There are various locations where we want to check if some rep is a 'plain'
// data edge, i.e. an external or flat rep. By having FLAT == EXTERNAL + 1, we
// can perform this check in a single branch as 'tag >= EXTERNAL'
// Likewise, we have some locations where we check for 'ring or external/flat',
// so likewise align RING to EXTERNAL.
// Note that we can leave this optimization to the compiler. The compiler will
// DTRT when it sees a condition like `tag == EXTERNAL || tag >= FLAT`.
static_assert(RING == BTREE + 1, "BTREE and RING not consecutive");
static_assert(EXTERNAL == RING + 1, "BTREE and EXTERNAL not consecutive");
static_assert(FLAT == EXTERNAL + 1, "EXTERNAL and FLAT not consecutive");
struct CordRep {
......@@ -283,15 +270,12 @@ struct CordRep {
// # LINT.ThenChange(cord_rep_btree.h:copy_raw)
// Returns true if this instance's tag matches the requested type.
constexpr bool IsRing() const { return tag == RING; }
constexpr bool IsSubstring() const { return tag == SUBSTRING; }
constexpr bool IsCrc() const { return tag == CRC; }
constexpr bool IsExternal() const { return tag == EXTERNAL; }
constexpr bool IsFlat() const { return tag >= FLAT; }
constexpr bool IsBtree() const { return tag == BTREE; }
inline CordRepRing* ring();
inline const CordRepRing* ring() const;
inline CordRepSubstring* substring();
inline const CordRepSubstring* substring() const;
inline CordRepCrc* crc();
......
// Copyright 2020 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_rep_ring.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <limits>
#include <memory>
#include <string>
#include "absl/base/internal/raw_logging.h"
#include "absl/base/internal/throw_delegate.h"
#include "absl/base/macros.h"
#include "absl/container/inlined_vector.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_consume.h"
#include "absl/strings/internal/cord_rep_flat.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
namespace {
using index_type = CordRepRing::index_type;
enum class Direction { kForward, kReversed };
inline bool IsFlatOrExternal(CordRep* rep) {
return rep->IsFlat() || rep->IsExternal();
}
// Verifies that n + extra <= kMaxCapacity: throws std::length_error otherwise.
inline void CheckCapacity(size_t n, size_t extra) {
if (ABSL_PREDICT_FALSE(extra > CordRepRing::kMaxCapacity - n)) {
base_internal::ThrowStdLengthError("Maximum capacity exceeded");
}
}
// Creates a flat from the provided string data, allocating up to `extra`
// capacity in the returned flat depending on kMaxFlatLength limitations.
// Requires `len` to be less or equal to `kMaxFlatLength`
CordRepFlat* CreateFlat(const char* s, size_t n, size_t extra = 0) { // NOLINT
assert(n <= kMaxFlatLength);
auto* rep = CordRepFlat::New(n + extra);
rep->length = n;
memcpy(rep->Data(), s, n);
return rep;
}
// Unrefs the entries in `[head, tail)`.
// Requires all entries to be a FLAT or EXTERNAL node.
void UnrefEntries(const CordRepRing* rep, index_type head, index_type tail) {
rep->ForEach(head, tail, [rep](index_type ix) {
CordRep* child = rep->entry_child(ix);
if (!child->refcount.Decrement()) {
if (child->tag >= FLAT) {
CordRepFlat::Delete(child->flat());
} else {
CordRepExternal::Delete(child->external());
}
}
});
}
} // namespace
std::ostream& operator<<(std::ostream& s, const CordRepRing& rep) {
// Note: 'pos' values are defined as size_t (for overflow reasons), but that
// prints really awkward for small prepended values such as -5. ssize_t is not
// portable (POSIX), so we use ptrdiff_t instead to cast to signed values.
s << " CordRepRing(" << &rep << ", length = " << rep.length
<< ", head = " << rep.head_ << ", tail = " << rep.tail_
<< ", cap = " << rep.capacity_ << ", rc = " << rep.refcount.Get()
<< ", begin_pos_ = " << static_cast<ptrdiff_t>(rep.begin_pos_) << ") {\n";
CordRepRing::index_type head = rep.head();
do {
CordRep* child = rep.entry_child(head);
s << " entry[" << head << "] length = " << rep.entry_length(head)
<< ", child " << child << ", clen = " << child->length
<< ", tag = " << static_cast<int>(child->tag)
<< ", rc = " << child->refcount.Get()
<< ", offset = " << rep.entry_data_offset(head)
<< ", end_pos = " << static_cast<ptrdiff_t>(rep.entry_end_pos(head))
<< "\n";
head = rep.advance(head);
} while (head != rep.tail());
return s << "}\n";
}
void CordRepRing::AddDataOffset(index_type index, size_t n) {
entry_data_offset()[index] += static_cast<offset_type>(n);
}
void CordRepRing::SubLength(index_type index, size_t n) {
entry_end_pos()[index] -= n;
}
class CordRepRing::Filler {
public:
Filler(CordRepRing* rep, index_type pos) : rep_(rep), head_(pos), pos_(pos) {}
index_type head() const { return head_; }
index_type pos() const { return pos_; }
void Add(CordRep* child, size_t offset, pos_type end_pos) {
rep_->entry_end_pos()[pos_] = end_pos;
rep_->entry_child()[pos_] = child;
rep_->entry_data_offset()[pos_] = static_cast<offset_type>(offset);
pos_ = rep_->advance(pos_);
}
private:
CordRepRing* rep_;
index_type head_;
index_type pos_;
};
#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
constexpr size_t CordRepRing::kMaxCapacity;
#endif
bool CordRepRing::IsValid(std::ostream& output) const {
if (capacity_ == 0) {
output << "capacity == 0";
return false;
}
if (head_ >= capacity_ || tail_ >= capacity_) {
output << "head " << head_ << " and/or tail " << tail_ << "exceed capacity "
<< capacity_;
return false;
}
const index_type back = retreat(tail_);
size_t pos_length = Distance(begin_pos_, entry_end_pos(back));
if (pos_length != length) {
output << "length " << length << " does not match positional length "
<< pos_length << " from begin_pos " << begin_pos_ << " and entry["
<< back << "].end_pos " << entry_end_pos(back);
return false;
}
index_type head = head_;
pos_type begin_pos = begin_pos_;
do {
pos_type end_pos = entry_end_pos(head);
size_t entry_length = Distance(begin_pos, end_pos);
if (entry_length == 0) {
output << "entry[" << head << "] has an invalid length " << entry_length
<< " from begin_pos " << begin_pos << " and end_pos " << end_pos;
return false;
}
CordRep* child = entry_child(head);
if (child == nullptr) {
output << "entry[" << head << "].child == nullptr";
return false;
}
if (child->tag < FLAT && child->tag != EXTERNAL) {
output << "entry[" << head << "].child has an invalid tag "
<< static_cast<int>(child->tag);
return false;
}
size_t offset = entry_data_offset(head);
if (offset >= child->length || entry_length > child->length - offset) {
output << "entry[" << head << "] has offset " << offset
<< " and entry length " << entry_length
<< " which are outside of the child's length of " << child->length;
return false;
}
begin_pos = end_pos;
head = advance(head);
} while (head != tail_);
return true;
}
#ifdef EXTRA_CORD_RING_VALIDATION
CordRepRing* CordRepRing::Validate(CordRepRing* rep, const char* file,
int line) {
if (!rep->IsValid(std::cerr)) {
std::cerr << "\nERROR: CordRepRing corrupted";
if (line) std::cerr << " at line " << line;
if (file) std::cerr << " in file " << file;
std::cerr << "\nContent = " << *rep;
abort();
}
return rep;
}
#endif // EXTRA_CORD_RING_VALIDATION
CordRepRing* CordRepRing::New(size_t capacity, size_t extra) {
CheckCapacity(capacity, extra);
size_t size = AllocSize(capacity += extra);
void* mem = ::operator new(size);
auto* rep = new (mem) CordRepRing(static_cast<index_type>(capacity));
rep->tag = RING;
rep->capacity_ = static_cast<index_type>(capacity);
rep->begin_pos_ = 0;
return rep;
}
void CordRepRing::SetCapacityForTesting(size_t capacity) {
// Adjust for the changed layout
assert(capacity <= capacity_);
assert(head() == 0 || head() < tail());
memmove(Layout::Partial(capacity).Pointer<1>(data_) + head(),
Layout::Partial(capacity_).Pointer<1>(data_) + head(),
entries() * sizeof(Layout::ElementType<1>));
memmove(Layout::Partial(capacity, capacity).Pointer<2>(data_) + head(),
Layout::Partial(capacity_, capacity_).Pointer<2>(data_) + head(),
entries() * sizeof(Layout::ElementType<2>));
capacity_ = static_cast<index_type>(capacity);
}
void CordRepRing::Delete(CordRepRing* rep) {
assert(rep != nullptr && rep->IsRing());
#if defined(__cpp_sized_deallocation)
size_t size = AllocSize(rep->capacity_);
rep->~CordRepRing();
::operator delete(rep, size);
#else
rep->~CordRepRing();
::operator delete(rep);
#endif
}
void CordRepRing::Destroy(CordRepRing* rep) {
UnrefEntries(rep, rep->head(), rep->tail());
Delete(rep);
}
template <bool ref>
void CordRepRing::Fill(const CordRepRing* src, index_type head,
index_type tail) {
this->length = src->length;
head_ = 0;
tail_ = advance(0, src->entries(head, tail));
begin_pos_ = src->begin_pos_;
// TODO(mvels): there may be opportunities here for large buffers.
auto* dst_pos = entry_end_pos();
auto* dst_child = entry_child();
auto* dst_offset = entry_data_offset();
src->ForEach(head, tail, [&](index_type index) {
*dst_pos++ = src->entry_end_pos(index);
CordRep* child = src->entry_child(index);
*dst_child++ = ref ? CordRep::Ref(child) : child;
*dst_offset++ = src->entry_data_offset(index);
});
}
CordRepRing* CordRepRing::Copy(CordRepRing* rep, index_type head,
index_type tail, size_t extra) {
CordRepRing* newrep = CordRepRing::New(rep->entries(head, tail), extra);
newrep->Fill<true>(rep, head, tail);
CordRep::Unref(rep);
return newrep;
}
CordRepRing* CordRepRing::Mutable(CordRepRing* rep, size_t extra) {
// Get current number of entries, and check for max capacity.
size_t entries = rep->entries();
if (!rep->refcount.IsOne()) {
return Copy(rep, rep->head(), rep->tail(), extra);
} else if (entries + extra > rep->capacity()) {
const size_t min_grow = rep->capacity() + rep->capacity() / 2;
const size_t min_extra = (std::max)(extra, min_grow - entries);
CordRepRing* newrep = CordRepRing::New(entries, min_extra);
newrep->Fill<false>(rep, rep->head(), rep->tail());
CordRepRing::Delete(rep);
return newrep;
} else {
return rep;
}
}
Span<char> CordRepRing::GetAppendBuffer(size_t size) {
assert(refcount.IsOne());
index_type back = retreat(tail_);
CordRep* child = entry_child(back);
if (child->tag >= FLAT && child->refcount.IsOne()) {
size_t capacity = child->flat()->Capacity();
pos_type end_pos = entry_end_pos(back);
size_t data_offset = entry_data_offset(back);
size_t entry_length = Distance(entry_begin_pos(back), end_pos);
size_t used = data_offset + entry_length;
if (size_t n = (std::min)(capacity - used, size)) {
child->length = data_offset + entry_length + n;
entry_end_pos()[back] = end_pos + n;
this->length += n;
return {child->flat()->Data() + used, n};
}
}
return {nullptr, 0};
}
Span<char> CordRepRing::GetPrependBuffer(size_t size) {
assert(refcount.IsOne());
CordRep* child = entry_child(head_);
size_t data_offset = entry_data_offset(head_);
if (data_offset && child->refcount.IsOne() && child->tag >= FLAT) {
size_t n = (std::min)(data_offset, size);
this->length += n;
begin_pos_ -= n;
data_offset -= n;
entry_data_offset()[head_] = static_cast<offset_type>(data_offset);
return {child->flat()->Data() + data_offset, n};
}
return {nullptr, 0};
}
CordRepRing* CordRepRing::CreateFromLeaf(CordRep* child, size_t offset,
size_t len, size_t extra) {
CordRepRing* rep = CordRepRing::New(1, extra);
rep->head_ = 0;
rep->tail_ = rep->advance(0);
rep->length = len;
rep->entry_end_pos()[0] = len;
rep->entry_child()[0] = child;
rep->entry_data_offset()[0] = static_cast<offset_type>(offset);
return Validate(rep);
}
CordRepRing* CordRepRing::CreateSlow(CordRep* child, size_t extra) {
CordRepRing* rep = nullptr;
Consume(child, [&](CordRep* child_arg, size_t offset, size_t len) {
if (IsFlatOrExternal(child_arg)) {
rep = rep ? AppendLeaf(rep, child_arg, offset, len)
: CreateFromLeaf(child_arg, offset, len, extra);
} else if (rep) {
rep = AddRing<AddMode::kAppend>(rep, child_arg->ring(), offset, len);
} else if (offset == 0 && child_arg->length == len) {
rep = Mutable(child_arg->ring(), extra);
} else {
rep = SubRing(child_arg->ring(), offset, len, extra);
}
});
return Validate(rep, nullptr, __LINE__);
}
CordRepRing* CordRepRing::Create(CordRep* child, size_t extra) {
size_t length = child->length;
if (IsFlatOrExternal(child)) {
return CreateFromLeaf(child, 0, length, extra);
}
if (child->IsRing()) {
return Mutable(child->ring(), extra);
}
return CreateSlow(child, extra);
}
template <CordRepRing::AddMode mode>
CordRepRing* CordRepRing::AddRing(CordRepRing* rep, CordRepRing* ring,
size_t offset, size_t len) {
assert(offset < ring->length);
constexpr bool append = mode == AddMode::kAppend;
Position head = ring->Find(offset);
Position tail = ring->FindTail(head.index, offset + len);
const index_type entries = ring->entries(head.index, tail.index);
rep = Mutable(rep, entries);
// The delta for making ring[head].end_pos into 'len - offset'
const pos_type delta_length =
(append ? rep->begin_pos_ + rep->length : rep->begin_pos_ - len) -
ring->entry_begin_pos(head.index) - head.offset;
// Start filling at `tail`, or `entries` before `head`
Filler filler(rep, append ? rep->tail_ : rep->retreat(rep->head_, entries));
if (ring->refcount.IsOne()) {
// Copy entries from source stealing the ref and adjusting the end position.
// Commit the filler as this is no-op.
ring->ForEach(head.index, tail.index, [&](index_type ix) {
filler.Add(ring->entry_child(ix), ring->entry_data_offset(ix),
ring->entry_end_pos(ix) + delta_length);
});
// Unref entries we did not copy over, and delete source.
if (head.index != ring->head_) UnrefEntries(ring, ring->head_, head.index);
if (tail.index != ring->tail_) UnrefEntries(ring, tail.index, ring->tail_);
CordRepRing::Delete(ring);
} else {
ring->ForEach(head.index, tail.index, [&](index_type ix) {
CordRep* child = ring->entry_child(ix);
filler.Add(child, ring->entry_data_offset(ix),
ring->entry_end_pos(ix) + delta_length);
CordRep::Ref(child);
});
CordRepRing::Unref(ring);
}
if (head.offset) {
// Increase offset of first 'source' entry appended or prepended.
// This is always the entry in `filler.head()`
rep->AddDataOffset(filler.head(), head.offset);
}
if (tail.offset) {
// Reduce length of last 'source' entry appended or prepended.
// This is always the entry tailed by `filler.pos()`
rep->SubLength(rep->retreat(filler.pos()), tail.offset);
}
// Commit changes
rep->length += len;
if (append) {
rep->tail_ = filler.pos();
} else {
rep->head_ = filler.head();
rep->begin_pos_ -= len;
}
return Validate(rep);
}
CordRepRing* CordRepRing::AppendSlow(CordRepRing* rep, CordRep* child) {
Consume(child, [&rep](CordRep* child_arg, size_t offset, size_t len) {
if (child_arg->IsRing()) {
rep = AddRing<AddMode::kAppend>(rep, child_arg->ring(), offset, len);
} else {
rep = AppendLeaf(rep, child_arg, offset, len);
}
});
return rep;
}
CordRepRing* CordRepRing::AppendLeaf(CordRepRing* rep, CordRep* child,
size_t offset, size_t len) {
rep = Mutable(rep, 1);
index_type back = rep->tail_;
const pos_type begin_pos = rep->begin_pos_ + rep->length;
rep->tail_ = rep->advance(rep->tail_);
rep->length += len;
rep->entry_end_pos()[back] = begin_pos + len;
rep->entry_child()[back] = child;
rep->entry_data_offset()[back] = static_cast<offset_type>(offset);
return Validate(rep, nullptr, __LINE__);
}
CordRepRing* CordRepRing::Append(CordRepRing* rep, CordRep* child) {
size_t length = child->length;
if (IsFlatOrExternal(child)) {
return AppendLeaf(rep, child, 0, length);
}
if (child->IsRing()) {
return AddRing<AddMode::kAppend>(rep, child->ring(), 0, length);
}
return AppendSlow(rep, child);
}
CordRepRing* CordRepRing::PrependSlow(CordRepRing* rep, CordRep* child) {
ReverseConsume(child, [&](CordRep* child_arg, size_t offset, size_t len) {
if (IsFlatOrExternal(child_arg)) {
rep = PrependLeaf(rep, child_arg, offset, len);
} else {
rep = AddRing<AddMode::kPrepend>(rep, child_arg->ring(), offset, len);
}
});
return Validate(rep);
}
CordRepRing* CordRepRing::PrependLeaf(CordRepRing* rep, CordRep* child,
size_t offset, size_t len) {
rep = Mutable(rep, 1);
index_type head = rep->retreat(rep->head_);
pos_type end_pos = rep->begin_pos_;
rep->head_ = head;
rep->length += len;
rep->begin_pos_ -= len;
rep->entry_end_pos()[head] = end_pos;
rep->entry_child()[head] = child;
rep->entry_data_offset()[head] = static_cast<offset_type>(offset);
return Validate(rep);
}
CordRepRing* CordRepRing::Prepend(CordRepRing* rep, CordRep* child) {
size_t length = child->length;
if (IsFlatOrExternal(child)) {
return PrependLeaf(rep, child, 0, length);
}
if (child->IsRing()) {
return AddRing<AddMode::kPrepend>(rep, child->ring(), 0, length);
}
return PrependSlow(rep, child);
}
CordRepRing* CordRepRing::Append(CordRepRing* rep, absl::string_view data,
size_t extra) {
if (rep->refcount.IsOne()) {
Span<char> avail = rep->GetAppendBuffer(data.length());
if (!avail.empty()) {
memcpy(avail.data(), data.data(), avail.length());
data.remove_prefix(avail.length());
}
}
if (data.empty()) return Validate(rep);
const size_t flats = (data.length() - 1) / kMaxFlatLength + 1;
rep = Mutable(rep, flats);
Filler filler(rep, rep->tail_);
pos_type pos = rep->begin_pos_ + rep->length;
while (data.length() >= kMaxFlatLength) {
auto* flat = CreateFlat(data.data(), kMaxFlatLength);
filler.Add(flat, 0, pos += kMaxFlatLength);
data.remove_prefix(kMaxFlatLength);
}
if (data.length()) {
auto* flat = CreateFlat(data.data(), data.length(), extra);
filler.Add(flat, 0, pos += data.length());
}
rep->length = pos - rep->begin_pos_;
rep->tail_ = filler.pos();
return Validate(rep);
}
CordRepRing* CordRepRing::Prepend(CordRepRing* rep, absl::string_view data,
size_t extra) {
if (rep->refcount.IsOne()) {
Span<char> avail = rep->GetPrependBuffer(data.length());
if (!avail.empty()) {
const char* tail = data.data() + data.length() - avail.length();
memcpy(avail.data(), tail, avail.length());
data.remove_suffix(avail.length());
}
}
if (data.empty()) return rep;
const size_t flats = (data.length() - 1) / kMaxFlatLength + 1;
rep = Mutable(rep, flats);
pos_type pos = rep->begin_pos_;
Filler filler(rep, rep->retreat(rep->head_, static_cast<index_type>(flats)));
size_t first_size = data.size() - (flats - 1) * kMaxFlatLength;
CordRepFlat* flat = CordRepFlat::New(first_size + extra);
flat->length = first_size + extra;
memcpy(flat->Data() + extra, data.data(), first_size);
data.remove_prefix(first_size);
filler.Add(flat, extra, pos);
pos -= first_size;
while (!data.empty()) {
assert(data.size() >= kMaxFlatLength);
flat = CreateFlat(data.data(), kMaxFlatLength);
filler.Add(flat, 0, pos);
pos -= kMaxFlatLength;
data.remove_prefix(kMaxFlatLength);
}
rep->head_ = filler.head();
rep->length += rep->begin_pos_ - pos;
rep->begin_pos_ = pos;
return Validate(rep);
}
// 32 entries is 32 * sizeof(pos_type) = 4 cache lines on x86
static constexpr index_type kBinarySearchThreshold = 32;
static constexpr index_type kBinarySearchEndCount = 8;
template <bool wrap>
CordRepRing::index_type CordRepRing::FindBinary(index_type head,
index_type tail,
size_t offset) const {
index_type count = tail + (wrap ? capacity_ : 0) - head;
do {
count = (count - 1) / 2;
assert(count < entries(head, tail_));
index_type mid = wrap ? advance(head, count) : head + count;
index_type after_mid = wrap ? advance(mid) : mid + 1;
bool larger = (offset >= entry_end_offset(mid));
head = larger ? after_mid : head;
tail = larger ? tail : mid;
assert(head != tail);
} while (ABSL_PREDICT_TRUE(count > kBinarySearchEndCount));
return head;
}
CordRepRing::Position CordRepRing::FindSlow(index_type head,
size_t offset) const {
index_type tail = tail_;
// Binary search until we are good for linear search
// Optimize for branchless / non wrapping ops
if (tail > head) {
index_type count = tail - head;
if (count > kBinarySearchThreshold) {
head = FindBinary<false>(head, tail, offset);
}
} else {
index_type count = capacity_ + tail - head;
if (count > kBinarySearchThreshold) {
head = FindBinary<true>(head, tail, offset);
}
}
pos_type pos = entry_begin_pos(head);
pos_type end_pos = entry_end_pos(head);
while (offset >= Distance(begin_pos_, end_pos)) {
head = advance(head);
pos = end_pos;
end_pos = entry_end_pos(head);
}
return {head, offset - Distance(begin_pos_, pos)};
}
CordRepRing::Position CordRepRing::FindTailSlow(index_type head,
size_t offset) const {
index_type tail = tail_;
const size_t tail_offset = offset - 1;
// Binary search until we are good for linear search
// Optimize for branchless / non wrapping ops
if (tail > head) {
index_type count = tail - head;
if (count > kBinarySearchThreshold) {
head = FindBinary<false>(head, tail, tail_offset);
}
} else {
index_type count = capacity_ + tail - head;
if (count > kBinarySearchThreshold) {
head = FindBinary<true>(head, tail, tail_offset);
}
}
size_t end_offset = entry_end_offset(head);
while (tail_offset >= end_offset) {
head = advance(head);
end_offset = entry_end_offset(head);
}
return {advance(head), end_offset - offset};
}
char CordRepRing::GetCharacter(size_t offset) const {
assert(offset < length);
Position pos = Find(offset);
size_t data_offset = entry_data_offset(pos.index) + pos.offset;
return GetRepData(entry_child(pos.index))[data_offset];
}
CordRepRing* CordRepRing::SubRing(CordRepRing* rep, size_t offset,
size_t len, size_t extra) {
assert(offset <= rep->length);
assert(offset <= rep->length - len);
if (len == 0) {
CordRep::Unref(rep);
return nullptr;
}
// Find position of first byte
Position head = rep->Find(offset);
Position tail = rep->FindTail(head.index, offset + len);
const size_t new_entries = rep->entries(head.index, tail.index);
if (rep->refcount.IsOne() && extra <= (rep->capacity() - new_entries)) {
// We adopt a privately owned rep and no extra entries needed.
if (head.index != rep->head_) UnrefEntries(rep, rep->head_, head.index);
if (tail.index != rep->tail_) UnrefEntries(rep, tail.index, rep->tail_);
rep->head_ = head.index;
rep->tail_ = tail.index;
} else {
// Copy subset to new rep
rep = Copy(rep, head.index, tail.index, extra);
head.index = rep->head_;
tail.index = rep->tail_;
}
// Adjust begin_pos and length
rep->length = len;
rep->begin_pos_ += offset;
// Adjust head and tail blocks
if (head.offset) {
rep->AddDataOffset(head.index, head.offset);
}
if (tail.offset) {
rep->SubLength(rep->retreat(tail.index), tail.offset);
}
return Validate(rep);
}
CordRepRing* CordRepRing::RemovePrefix(CordRepRing* rep, size_t len,
size_t extra) {
assert(len <= rep->length);
if (len == rep->length) {
CordRep::Unref(rep);
return nullptr;
}
Position head = rep->Find(len);
if (rep->refcount.IsOne()) {
if (head.index != rep->head_) UnrefEntries(rep, rep->head_, head.index);
rep->head_ = head.index;
} else {
rep = Copy(rep, head.index, rep->tail_, extra);
head.index = rep->head_;
}
// Adjust begin_pos and length
rep->length -= len;
rep->begin_pos_ += len;
// Adjust head block
if (head.offset) {
rep->AddDataOffset(head.index, head.offset);
}
return Validate(rep);
}
CordRepRing* CordRepRing::RemoveSuffix(CordRepRing* rep, size_t len,
size_t extra) {
assert(len <= rep->length);
if (len == rep->length) {
CordRep::Unref(rep);
return nullptr;
}
Position tail = rep->FindTail(rep->length - len);
if (rep->refcount.IsOne()) {
// We adopt a privately owned rep, scrub.
if (tail.index != rep->tail_) UnrefEntries(rep, tail.index, rep->tail_);
rep->tail_ = tail.index;
} else {
// Copy subset to new rep
rep = Copy(rep, rep->head_, tail.index, extra);
tail.index = rep->tail_;
}
// Adjust length
rep->length -= len;
// Adjust tail block
if (tail.offset) {
rep->SubLength(rep->retreat(tail.index), tail.offset);
}
return Validate(rep);
}
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
// Copyright 2020 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.
#ifndef ABSL_STRINGS_INTERNAL_CORD_REP_RING_H_
#define ABSL_STRINGS_INTERNAL_CORD_REP_RING_H_
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iosfwd>
#include <limits>
#include <memory>
#include "absl/container/internal/layout.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_flat.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
// All operations modifying a ring buffer are implemented as static methods
// requiring a CordRepRing instance with a reference adopted by the method.
//
// The methods return the modified ring buffer, which may be equal to the input
// if the input was not shared, and having large enough capacity to accommodate
// any newly added node(s). Otherwise, a copy of the input rep with the new
// node(s) added is returned.
//
// Any modification on non shared ring buffers with enough capacity will then
// require minimum atomic operations. Caller should where possible provide
// reasonable `extra` hints for both anticipated extra `flat` byte space, as
// well as anticipated extra nodes required for complex operations.
//
// Example of code creating a ring buffer, adding some data to it,
// and discarding the buffer when done:
//
// void FunWithRings() {
// // Create ring with 3 flats
// CordRep* flat = CreateFlat("Hello");
// CordRepRing* ring = CordRepRing::Create(flat, 2);
// ring = CordRepRing::Append(ring, CreateFlat(" "));
// ring = CordRepRing::Append(ring, CreateFlat("world"));
// DoSomethingWithRing(ring);
// CordRep::Unref(ring);
// }
//
// Example of code Copying an existing ring buffer and modifying it:
//
// void MoreFunWithRings(CordRepRing* src) {
// CordRepRing* ring = CordRep::Ref(src)->ring();
// ring = CordRepRing::Append(ring, CreateFlat("Hello"));
// ring = CordRepRing::Append(ring, CreateFlat(" "));
// ring = CordRepRing::Append(ring, CreateFlat("world"));
// DoSomethingWithRing(ring);
// CordRep::Unref(ring);
// }
//
class CordRepRing : public CordRep {
public:
// `pos_type` represents a 'logical position'. A CordRepRing instance has a
// `begin_pos` (default 0), and each node inside the buffer will have an
// `end_pos` which is the `end_pos` of the previous node (or `begin_pos`) plus
// this node's length. The purpose is to allow for a binary search on this
// position, while allowing O(1) prepend and append operations.
using pos_type = size_t;
// `index_type` is the type for the `head`, `tail` and `capacity` indexes.
// Ring buffers are limited to having no more than four billion entries.
using index_type = uint32_t;
// `offset_type` is the type for the data offset inside a child rep's data.
using offset_type = uint32_t;
// Position holds the node index and relative offset into the node for
// some physical offset in the contained data as returned by the Find()
// and FindTail() methods.
struct Position {
index_type index;
size_t offset;
};
// The maximum # of child nodes that can be hosted inside a CordRepRing.
static constexpr size_t kMaxCapacity = (std::numeric_limits<uint32_t>::max)();
// CordRepring can not be default constructed, moved, copied or assigned.
CordRepRing() = delete;
CordRepRing(const CordRepRing&) = delete;
CordRepRing& operator=(const CordRepRing&) = delete;
// Returns true if this instance is valid, false if some or all of the
// invariants are broken. Intended for debug purposes only.
// `output` receives an explanation of the broken invariants.
bool IsValid(std::ostream& output) const;
// Returns the size in bytes for a CordRepRing with `capacity' entries.
static constexpr size_t AllocSize(size_t capacity);
// Returns the distance in bytes from `pos` to `end_pos`.
static constexpr size_t Distance(pos_type pos, pos_type end_pos);
// Creates a new ring buffer from the provided `rep`. Adopts a reference
// on `rep`. The returned ring buffer has a capacity of at least `extra + 1`
static CordRepRing* Create(CordRep* child, size_t extra = 0);
// `head`, `tail` and `capacity` indexes defining the ring buffer boundaries.
index_type head() const { return head_; }
index_type tail() const { return tail_; }
index_type capacity() const { return capacity_; }
// Returns the number of entries in this instance.
index_type entries() const { return entries(head_, tail_); }
// Returns the logical begin position of this instance.
pos_type begin_pos() const { return begin_pos_; }
// Returns the number of entries for a given head-tail range.
// Requires `head` and `tail` values to be less than `capacity()`.
index_type entries(index_type head, index_type tail) const {
assert(head < capacity_ && tail < capacity_);
return tail - head + ((tail > head) ? 0 : capacity_);
}
// Returns the logical end position of entry `index`.
pos_type const& entry_end_pos(index_type index) const {
assert(IsValidIndex(index));
return Layout::Partial().Pointer<0>(data_)[index];
}
// Returns the child pointer of entry `index`.
CordRep* const& entry_child(index_type index) const {
assert(IsValidIndex(index));
return Layout::Partial(capacity()).Pointer<1>(data_)[index];
}
// Returns the data offset of entry `index`
offset_type const& entry_data_offset(index_type index) const {
assert(IsValidIndex(index));
return Layout::Partial(capacity(), capacity()).Pointer<2>(data_)[index];
}
// Appends the provided child node to the `rep` instance.
// Adopts a reference from `rep` and `child` which may not be null.
// If the provided child is a FLAT or EXTERNAL node, or a SUBSTRING node
// containing a FLAT or EXTERNAL node, then flat or external the node is added
// 'as is', with an offset added for the SUBSTRING case.
// If the provided child is a RING or CONCAT tree, or a SUBSTRING of a RING or
// CONCAT tree, then all child nodes not excluded by any start offset or
// length values are added recursively.
static CordRepRing* Append(CordRepRing* rep, CordRep* child);
// Appends the provided string data to the `rep` instance.
// This function will attempt to utilize any remaining capacity in the last
// node of the input if that node is not shared (directly or indirectly), and
// of type FLAT. Remaining data will be added as one or more FLAT nodes.
// Any last node added to the ring buffer will be allocated with up to
// `extra` bytes of capacity for (anticipated) subsequent append actions.
static CordRepRing* Append(CordRepRing* rep, string_view data,
size_t extra = 0);
// Prepends the provided child node to the `rep` instance.
// Adopts a reference from `rep` and `child` which may not be null.
// If the provided child is a FLAT or EXTERNAL node, or a SUBSTRING node
// containing a FLAT or EXTERNAL node, then flat or external the node is
// prepended 'as is', with an optional offset added for the SUBSTRING case.
// If the provided child is a RING or CONCAT tree, or a SUBSTRING of a RING
// or CONCAT tree, then all child nodes not excluded by any start offset or
// length values are added recursively.
static CordRepRing* Prepend(CordRepRing* rep, CordRep* child);
// Prepends the provided string data to the `rep` instance.
// This function will attempt to utilize any remaining capacity in the first
// node of the input if that node is not shared (directly or indirectly), and
// of type FLAT. Remaining data will be added as one or more FLAT nodes.
// Any first node prepnded to the ring buffer will be allocated with up to
// `extra` bytes of capacity for (anticipated) subsequent prepend actions.
static CordRepRing* Prepend(CordRepRing* rep, string_view data,
size_t extra = 0);
// Returns a span referencing potentially unused capacity in the last node.
// The returned span may be empty if no such capacity is available, or if the
// current instance is shared. Else, a span of size `n <= size` is returned.
// If non empty, the ring buffer is adjusted to the new length, with the newly
// added capacity left uninitialized. Callers should assign a value to the
// entire span before any other operations on this instance.
Span<char> GetAppendBuffer(size_t size);
// Returns a span referencing potentially unused capacity in the first node.
// This function is identical to GetAppendBuffer except that it returns a span
// referencing up to `size` capacity directly before the existing data.
Span<char> GetPrependBuffer(size_t size);
// Returns a cord ring buffer containing `len` bytes of data starting at
// `offset`. If the input is not shared, this function will remove all head
// and tail child nodes outside of the requested range, and adjust the new
// head and tail nodes as required. If the input is shared, this function
// returns a new instance sharing some or all of the nodes from the input.
static CordRepRing* SubRing(CordRepRing* r, size_t offset, size_t len,
size_t extra = 0);
// Returns a cord ring buffer with the first `len` bytes removed.
// If the input is not shared, this function will remove all head child nodes
// fully inside the first `length` bytes, and adjust the new head as required.
// If the input is shared, this function returns a new instance sharing some
// or all of the nodes from the input.
static CordRepRing* RemoveSuffix(CordRepRing* r, size_t len,
size_t extra = 0);
// Returns a cord ring buffer with the last `len` bytes removed.
// If the input is not shared, this function will remove all head child nodes
// fully inside the first `length` bytes, and adjust the new head as required.
// If the input is shared, this function returns a new instance sharing some
// or all of the nodes from the input.
static CordRepRing* RemovePrefix(CordRepRing* r, size_t len,
size_t extra = 0);
// Returns the character at `offset`. Requires that `offset < length`.
char GetCharacter(size_t offset) const;
// Returns true if this instance manages a single contiguous buffer, in which
// case the (optional) output parameter `fragment` is set. Otherwise, the
// function returns false, and `fragment` is left unchanged.
bool IsFlat(absl::string_view* fragment) const;
// Returns true if the data starting at `offset` with length `len` is
// managed by this instance inside a single contiguous buffer, in which case
// the (optional) output parameter `fragment` is set to the contiguous memory
// starting at offset `offset` with length `length`. Otherwise, the function
// returns false, and `fragment` is left unchanged.
bool IsFlat(size_t offset, size_t len, absl::string_view* fragment) const;
// Testing only: set capacity to requested capacity.
void SetCapacityForTesting(size_t capacity);
// Returns the CordRep data pointer for the provided CordRep.
// Requires that the provided `rep` is either a FLAT or EXTERNAL CordRep.
static const char* GetLeafData(const CordRep* rep);
// Returns the CordRep data pointer for the provided CordRep.
// Requires that `rep` is either a FLAT, EXTERNAL, or SUBSTRING CordRep.
static const char* GetRepData(const CordRep* rep);
// Advances the provided position, wrapping around capacity as needed.
// Requires `index` < capacity()
inline index_type advance(index_type index) const;
// Advances the provided position by 'n`, wrapping around capacity as needed.
// Requires `index` < capacity() and `n` <= capacity.
inline index_type advance(index_type index, index_type n) const;
// Retreats the provided position, wrapping around 0 as needed.
// Requires `index` < capacity()
inline index_type retreat(index_type index) const;
// Retreats the provided position by 'n', wrapping around 0 as needed.
// Requires `index` < capacity()
inline index_type retreat(index_type index, index_type n) const;
// Returns the logical begin position of entry `index`
pos_type const& entry_begin_pos(index_type index) const {
return (index == head_) ? begin_pos_ : entry_end_pos(retreat(index));
}
// Returns the physical start offset of entry `index`
size_t entry_start_offset(index_type index) const {
return Distance(begin_pos_, entry_begin_pos(index));
}
// Returns the physical end offset of entry `index`
size_t entry_end_offset(index_type index) const {
return Distance(begin_pos_, entry_end_pos(index));
}
// Returns the data length for entry `index`
size_t entry_length(index_type index) const {
return Distance(entry_begin_pos(index), entry_end_pos(index));
}
// Returns the data for entry `index`
absl::string_view entry_data(index_type index) const;
// Returns the position for `offset` as {index, prefix}. `index` holds the
// index of the entry at the specified offset and `prefix` holds the relative
// offset inside that entry.
// Requires `offset` < length.
//
// For example we can implement GetCharacter(offset) as:
// char GetCharacter(size_t offset) {
// Position pos = this->Find(offset);
// return this->entry_data(pos.pos)[pos.offset];
// }
inline Position Find(size_t offset) const;
// Find starting at `head`
inline Position Find(index_type head, size_t offset) const;
// Returns the tail position for `offset` as {tail index, suffix}.
// `tail index` holds holds the index of the entry holding the offset directly
// before 'offset` advanced by one. 'suffix` holds the relative offset from
// that relative offset in the entry to the end of the entry.
// For example, FindTail(length) will return {tail(), 0}, FindTail(length - 5)
// will return {retreat(tail), 5)} provided the preceding entry contains at
// least 5 bytes of data.
// Requires offset >= 1 && offset <= length.
//
// This function is very useful in functions that need to clip the end of some
// ring buffer such as 'RemovePrefix'.
// For example, we could implement RemovePrefix for non shared instances as:
// void RemoveSuffix(size_t n) {
// Position pos = FindTail(length - n);
// UnrefEntries(pos.pos, this->tail_);
// this->tail_ = pos.pos;
// entry(retreat(pos.pos)).end_pos -= pos.offset;
// }
inline Position FindTail(size_t offset) const;
// Find tail starting at `head`
inline Position FindTail(index_type head, size_t offset) const;
// Invokes f(index_type index) for each entry inside the range [head, tail>
template <typename F>
void ForEach(index_type head, index_type tail, F&& f) const {
index_type n1 = (tail > head) ? tail : capacity_;
for (index_type i = head; i < n1; ++i) f(i);
if (tail <= head) {
for (index_type i = 0; i < tail; ++i) f(i);
}
}
// Invokes f(index_type index) for each entry inside this instance.
template <typename F>
void ForEach(F&& f) const {
ForEach(head_, tail_, std::forward<F>(f));
}
// Dump this instance's data tp stream `s` in human readable format, excluding
// the actual data content itself. Intended for debug purposes only.
friend std::ostream& operator<<(std::ostream& s, const CordRepRing& rep);
private:
enum class AddMode { kAppend, kPrepend };
using Layout = container_internal::Layout<pos_type, CordRep*, offset_type>;
class Filler;
class Transaction;
class CreateTransaction;
static constexpr size_t kLayoutAlignment = Layout::Partial().Alignment();
// Creates a new CordRepRing.
explicit CordRepRing(index_type capacity) : capacity_(capacity) {}
// Returns true if `index` is a valid index into this instance.
bool IsValidIndex(index_type index) const;
// Debug use only: validates the provided CordRepRing invariants.
// Verification of all CordRepRing methods can be enabled by defining
// EXTRA_CORD_RING_VALIDATION, i.e.: `--copts=-DEXTRA_CORD_RING_VALIDATION`
// Verification is VERY expensive, so only do it for debugging purposes.
static CordRepRing* Validate(CordRepRing* rep, const char* file = nullptr,
int line = 0);
// Allocates a CordRepRing large enough to hold `capacity + extra' entries.
// The returned capacity may be larger if the allocated memory allows for it.
// The maximum capacity of a CordRepRing is capped at kMaxCapacity.
// Throws `std::length_error` if `capacity + extra' exceeds kMaxCapacity.
static CordRepRing* New(size_t capacity, size_t extra);
// Deallocates (but does not destroy) the provided ring buffer.
static void Delete(CordRepRing* rep);
// Destroys the provided ring buffer, decrementing the reference count of all
// contained child CordReps. The provided 1\`rep` should have a ref count of
// one (pre decrement destroy call observing `refcount.IsOne()`) or zero
// (post decrement destroy call observing `!refcount.Decrement()`).
static void Destroy(CordRepRing* rep);
// Returns a mutable reference to the logical end position array.
pos_type* entry_end_pos() {
return Layout::Partial().Pointer<0>(data_);
}
// Returns a mutable reference to the child pointer array.
CordRep** entry_child() {
return Layout::Partial(capacity()).Pointer<1>(data_);
}
// Returns a mutable reference to the data offset array.
offset_type* entry_data_offset() {
return Layout::Partial(capacity(), capacity()).Pointer<2>(data_);
}
// Find implementations for the non fast path 0 / length cases.
Position FindSlow(index_type head, size_t offset) const;
Position FindTailSlow(index_type head, size_t offset) const;
// Finds the index of the first node that is inside a reasonable distance
// of the node at `offset` from which we can continue with a linear search.
template <bool wrap>
index_type FindBinary(index_type head, index_type tail, size_t offset) const;
// Fills the current (initialized) instance from the provided source, copying
// entries [head, tail). Adds a reference to copied entries if `ref` is true.
template <bool ref>
void Fill(const CordRepRing* src, index_type head, index_type tail);
// Create a copy of 'rep', copying all entries [head, tail), allocating room
// for `extra` entries. Adds a reference on all copied entries.
static CordRepRing* Copy(CordRepRing* rep, index_type head, index_type tail,
size_t extra = 0);
// Returns a Mutable CordRepRing reference from `rep` with room for at least
// `extra` additional nodes. Adopts a reference count from `rep`.
// This function will return `rep` if, and only if:
// - rep.entries + extra <= rep.capacity
// - rep.refcount == 1
// Otherwise, this function will create a new copy of `rep` with additional
// capacity to satisfy `extra` extra nodes, and unref the old `rep` instance.
//
// If a new CordRepRing can not be allocated, or the new capacity would exceed
// the maximum capacity, then the input is consumed only, and an exception is
// thrown.
static CordRepRing* Mutable(CordRepRing* rep, size_t extra);
// Slow path for Append(CordRepRing* rep, CordRep* child). This function is
// exercised if the provided `child` in Append() is not a leaf node, i.e., a
// ring buffer or old (concat) cord tree.
static CordRepRing* AppendSlow(CordRepRing* rep, CordRep* child);
// Appends the provided leaf node. Requires `child` to be FLAT or EXTERNAL.
static CordRepRing* AppendLeaf(CordRepRing* rep, CordRep* child,
size_t offset, size_t length);
// Prepends the provided leaf node. Requires `child` to be FLAT or EXTERNAL.
static CordRepRing* PrependLeaf(CordRepRing* rep, CordRep* child,
size_t offset, size_t length);
// Slow path for Prepend(CordRepRing* rep, CordRep* child). This function is
// exercised if the provided `child` in Prepend() is not a leaf node, i.e., a
// ring buffer or old (concat) cord tree.
static CordRepRing* PrependSlow(CordRepRing* rep, CordRep* child);
// Slow path for Create(CordRep* child, size_t extra). This function is
// exercised if the provided `child` in Prepend() is not a leaf node, i.e., a
// ring buffer or old (concat) cord tree.
static CordRepRing* CreateSlow(CordRep* child, size_t extra);
// Creates a new ring buffer from the provided `child` leaf node. Requires
// `child` to be FLAT or EXTERNAL. on `rep`.
// The returned ring buffer has a capacity of at least `1 + extra`
static CordRepRing* CreateFromLeaf(CordRep* child, size_t offset,
size_t length, size_t extra);
// Appends or prepends (depending on AddMode) the ring buffer in `ring' to
// `rep` starting at `offset` with length `len`.
template <AddMode mode>
static CordRepRing* AddRing(CordRepRing* rep, CordRepRing* ring,
size_t offset, size_t len);
// Increases the data offset for entry `index` by `n`.
void AddDataOffset(index_type index, size_t n);
// Decreases the length for entry `index` by `n`.
void SubLength(index_type index, size_t n);
index_type head_;
index_type tail_;
index_type capacity_;
pos_type begin_pos_;
alignas(kLayoutAlignment) char data_[kLayoutAlignment];
friend struct CordRep;
};
constexpr size_t CordRepRing::AllocSize(size_t capacity) {
return sizeof(CordRepRing) - sizeof(data_) +
Layout(capacity, capacity, capacity).AllocSize();
}
inline constexpr size_t CordRepRing::Distance(pos_type pos, pos_type end_pos) {
return (end_pos - pos);
}
inline const char* CordRepRing::GetLeafData(const CordRep* rep) {
return rep->tag != EXTERNAL ? rep->flat()->Data() : rep->external()->base;
}
inline const char* CordRepRing::GetRepData(const CordRep* rep) {
if (rep->tag >= FLAT) return rep->flat()->Data();
if (rep->tag == EXTERNAL) return rep->external()->base;
return GetLeafData(rep->substring()->child) + rep->substring()->start;
}
inline CordRepRing::index_type CordRepRing::advance(index_type index) const {
assert(index < capacity_);
return ++index == capacity_ ? 0 : index;
}
inline CordRepRing::index_type CordRepRing::advance(index_type index,
index_type n) const {
assert(index < capacity_ && n <= capacity_);
return (index += n) >= capacity_ ? index - capacity_ : index;
}
inline CordRepRing::index_type CordRepRing::retreat(index_type index) const {
assert(index < capacity_);
return (index > 0 ? index : capacity_) - 1;
}
inline CordRepRing::index_type CordRepRing::retreat(index_type index,
index_type n) const {
assert(index < capacity_ && n <= capacity_);
return index >= n ? index - n : capacity_ - n + index;
}
inline absl::string_view CordRepRing::entry_data(index_type index) const {
size_t data_offset = entry_data_offset(index);
return {GetRepData(entry_child(index)) + data_offset, entry_length(index)};
}
inline bool CordRepRing::IsValidIndex(index_type index) const {
if (index >= capacity_) return false;
return (tail_ > head_) ? (index >= head_ && index < tail_)
: (index >= head_ || index < tail_);
}
#ifndef EXTRA_CORD_RING_VALIDATION
inline CordRepRing* CordRepRing::Validate(CordRepRing* rep,
const char* /*file*/, int /*line*/) {
return rep;
}
#endif
inline CordRepRing::Position CordRepRing::Find(size_t offset) const {
assert(offset < length);
return (offset == 0) ? Position{head_, 0} : FindSlow(head_, offset);
}
inline CordRepRing::Position CordRepRing::Find(index_type head,
size_t offset) const {
assert(offset < length);
assert(IsValidIndex(head) && offset >= entry_start_offset(head));
return (offset == 0) ? Position{head_, 0} : FindSlow(head, offset);
}
inline CordRepRing::Position CordRepRing::FindTail(size_t offset) const {
assert(offset > 0 && offset <= length);
return (offset == length) ? Position{tail_, 0} : FindTailSlow(head_, offset);
}
inline CordRepRing::Position CordRepRing::FindTail(index_type head,
size_t offset) const {
assert(offset > 0 && offset <= length);
assert(IsValidIndex(head) && offset >= entry_start_offset(head) + 1);
return (offset == length) ? Position{tail_, 0} : FindTailSlow(head, offset);
}
// Now that CordRepRing is defined, we can define CordRep's helper casts:
inline CordRepRing* CordRep::ring() {
assert(IsRing());
return static_cast<CordRepRing*>(this);
}
inline const CordRepRing* CordRep::ring() const {
assert(IsRing());
return static_cast<const CordRepRing*>(this);
}
inline bool CordRepRing::IsFlat(absl::string_view* fragment) const {
if (entries() == 1) {
if (fragment) *fragment = entry_data(head());
return true;
}
return false;
}
inline bool CordRepRing::IsFlat(size_t offset, size_t len,
absl::string_view* fragment) const {
const Position pos = Find(offset);
const absl::string_view data = entry_data(pos.index);
if (data.length() >= len && data.length() - len >= pos.offset) {
if (fragment) *fragment = data.substr(pos.offset, len);
return true;
}
return false;
}
std::ostream& operator<<(std::ostream& s, const CordRepRing& rep);
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_STRINGS_INTERNAL_CORD_REP_RING_H_
// 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.
#ifndef ABSL_STRINGS_INTERNAL_CORD_REP_RING_READER_H_
#define ABSL_STRINGS_INTERNAL_CORD_REP_RING_READER_H_
#include <cassert>
#include <cstddef>
#include <cstdint>
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
// CordRepRingReader provides basic navigation over CordRepRing data.
class CordRepRingReader {
public:
// Returns true if this instance is not empty.
explicit operator bool() const { return ring_ != nullptr; }
// Returns the ring buffer reference for this instance, or nullptr if empty.
CordRepRing* ring() const { return ring_; }
// Returns the current node index inside the ring buffer for this instance.
// The returned value is undefined if this instance is empty.
CordRepRing::index_type index() const { return index_; }
// Returns the current node inside the ring buffer for this instance.
// The returned value is undefined if this instance is empty.
CordRep* node() const { return ring_->entry_child(index_); }
// Returns the length of the referenced ring buffer.
// Requires the current instance to be non empty.
size_t length() const {
assert(ring_);
return ring_->length;
}
// Returns the end offset of the last navigated-to chunk, which represents the
// total bytes 'consumed' relative to the start of the ring. The returned
// value is never zero. For example, initializing a reader with a ring buffer
// with a first chunk of 19 bytes will return consumed() = 19.
// Requires the current instance to be non empty.
size_t consumed() const {
assert(ring_);
return ring_->entry_end_offset(index_);
}
// Returns the number of bytes remaining beyond the last navigated-to chunk.
// Requires the current instance to be non empty.
size_t remaining() const {
assert(ring_);
return length() - consumed();
}
// Resets this instance to an empty value
void Reset() { ring_ = nullptr; }
// Resets this instance to the start of `ring`. `ring` must not be null.
// Returns a reference into the first chunk of the provided ring.
absl::string_view Reset(CordRepRing* ring) {
assert(ring);
ring_ = ring;
index_ = ring_->head();
return ring_->entry_data(index_);
}
// Navigates to the next chunk inside the reference ring buffer.
// Returns a reference into the navigated-to chunk.
// Requires remaining() to be non zero.
absl::string_view Next() {
assert(remaining());
index_ = ring_->advance(index_);
return ring_->entry_data(index_);
}
// Navigates to the chunk at offset `offset`.
// Returns a reference into the navigated-to chunk, adjusted for the relative
// position of `offset` into that chunk. For example, calling Seek(13) on a
// ring buffer containing 2 chunks of 10 and 20 bytes respectively will return
// a string view into the second chunk starting at offset 3 with a size of 17.
// Requires `offset` to be less than `length()`
absl::string_view Seek(size_t offset) {
assert(offset < length());
size_t current = ring_->entry_end_offset(index_);
CordRepRing::index_type hint = (offset >= current) ? index_ : ring_->head();
const CordRepRing::Position head = ring_->Find(hint, offset);
index_ = head.index;
auto data = ring_->entry_data(head.index);
data.remove_prefix(head.offset);
return data;
}
private:
CordRepRing* ring_ = nullptr;
CordRepRing::index_type index_;
};
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_STRINGS_INTERNAL_CORD_REP_RING_READER_H_
......@@ -21,7 +21,6 @@
#include "absl/strings/internal/cord_internal.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/cordz_handle.h"
#include "absl/strings/internal/cordz_statistics.h"
#include "absl/strings/internal/cordz_update_tracker.h"
......@@ -97,14 +96,11 @@ class CordRepAnalyzer {
repref = CountLinearReps(repref, memory_usage_);
switch (repref.tag()) {
case CordRepKind::RING:
AnalyzeRing(repref);
break;
case CordRepKind::BTREE:
AnalyzeBtree(repref);
break;
default:
// We should have either a btree or ring node if not null.
// We should have a btree node if not null.
ABSL_ASSERT(repref.tag() == CordRepKind::UNUSED_0);
break;
}
......@@ -204,17 +200,6 @@ class CordRepAnalyzer {
return rep;
}
// Analyzes the provided ring.
void AnalyzeRing(RepRef rep) {
statistics_.node_count++;
statistics_.node_counts.ring++;
const CordRepRing* ring = rep.rep->ring();
memory_usage_.Add(CordRepRing::AllocSize(ring->capacity()), rep.refcount);
ring->ForEach([&](CordRepRing::index_type pos) {
CountLinearReps(rep.Child(ring->entry_child(pos)), memory_usage_);
});
}
// Analyzes the provided btree.
void AnalyzeBtree(RepRef rep) {
statistics_.node_count++;
......
......@@ -25,7 +25,6 @@
#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_ring.h"
#include "absl/strings/internal/cordz_info.h"
#include "absl/strings/internal/cordz_sample_token.h"
#include "absl/strings/internal/cordz_statistics.h"
......@@ -123,11 +122,6 @@ size_t SizeOf(const CordRepExternal* rep) {
return sizeof(CordRepExternalImpl<intptr_t>) + rep->length;
}
template <>
size_t SizeOf(const CordRepRing* rep) {
return CordRepRing::AllocSize(rep->capacity());
}
// Computes fair share memory used in a naive 'we dare to recurse' way.
double FairShareImpl(CordRep* rep, size_t ref) {
double self = 0.0, children = 0.0;
......@@ -144,11 +138,6 @@ double FairShareImpl(CordRep* rep, size_t ref) {
for (CordRep*edge : rep->btree()->Edges()) {
children += FairShareImpl(edge, ref);
}
} else if (rep->tag == RING) {
self = SizeOf(rep->ring());
rep->ring()->ForEach([&](CordRepRing::index_type i) {
self += FairShareImpl(rep->ring()->entry_child(i), 1);
});
} else {
assert(false);
}
......@@ -294,64 +283,6 @@ TEST(CordzInfoStatisticsTest, SharedSubstring) {
EXPECT_THAT(SampleCord(substring), EqStatistics(expected));
}
TEST(CordzInfoStatisticsTest, Ring) {
RefHelper ref;
auto* flat1 = Flat(240);
auto* flat2 = Flat(2000);
auto* flat3 = Flat(70);
auto* external = External(3000);
CordRepRing* ring = CordRepRing::Create(flat1);
ring = CordRepRing::Append(ring, flat2);
ring = CordRepRing::Append(ring, flat3);
ring = ref.NeedsUnref(CordRepRing::Append(ring, external));
CordzStatistics expected;
expected.size = ring->length;
expected.estimated_memory_usage = SizeOf(ring) + SizeOf(flat1) +
SizeOf(flat2) + SizeOf(flat3) +
SizeOf(external);
expected.estimated_fair_share_memory_usage = expected.estimated_memory_usage;
expected.node_count = 5;
expected.node_counts.flat = 3;
expected.node_counts.flat_128 = 1;
expected.node_counts.flat_256 = 1;
expected.node_counts.external = 1;
expected.node_counts.ring = 1;
EXPECT_THAT(SampleCord(ring), EqStatistics(expected));
}
TEST(CordzInfoStatisticsTest, SharedSubstringRing) {
RefHelper ref;
auto* flat1 = ref.Ref(Flat(240));
auto* flat2 = Flat(200);
auto* flat3 = Flat(70);
auto* external = ref.Ref(External(3000), 5);
CordRepRing* ring = CordRepRing::Create(flat1);
ring = CordRepRing::Append(ring, flat2);
ring = CordRepRing::Append(ring, flat3);
ring = ref.Ref(CordRepRing::Append(ring, external), 4);
auto* substring = ref.Ref(ref.NeedsUnref(Substring(ring)));
CordzStatistics expected;
expected.size = substring->length;
expected.estimated_memory_usage = SizeOf(ring) + SizeOf(flat1) +
SizeOf(flat2) + SizeOf(flat3) +
SizeOf(external) + SizeOf(substring);
expected.estimated_fair_share_memory_usage = FairShare(substring);
expected.node_count = 6;
expected.node_counts.flat = 3;
expected.node_counts.flat_128 = 1;
expected.node_counts.flat_256 = 2;
expected.node_counts.external = 1;
expected.node_counts.ring = 1;
expected.node_counts.substring = 1;
EXPECT_THAT(SampleCord(substring), EqStatistics(expected));
}
TEST(CordzInfoStatisticsTest, BtreeLeaf) {
ASSERT_THAT(CordRepBtree::kMaxCapacity, Ge(3u));
RefHelper ref;
......@@ -528,15 +459,11 @@ TEST(CordzInfoStatisticsTest, ThreadSafety) {
CordRep::Unref(cord.as_tree());
cord.set_inline_size(0);
} else {
// Coin toss to 25% ring, 25% btree, and 50% flat.
// Coin toss to 50% btree, and 50% flat.
CordRep* rep = Flat(256);
if (coin_toss(gen) != 0) {
if (coin_toss(gen) != 0) {
rep = CordRepRing::Create(rep);
} else {
rep = CordRepBtree::Create(rep);
}
}
// Maybe CRC this cord
if (dice_roll(gen) == 6) {
......
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