#include <cstddef>
#include <cstdint>
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/container/inlined_vector.h"
#include "absl/strings/cord_analysis.h"
#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_flat.h"
#include "absl/strings/internal/cord_rep_ring.h"
#include "absl/base/macros.h"
#include "absl/base/port.h"
#include "absl/functional/function_ref.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
namespace {
enum class Mode { kTotal, kFairShare };
template <Mode mode>
struct CordRepRef {
explicit CordRepRef(const CordRep* r) : rep(r) {}
CordRepRef Child(const CordRep* child) const { return CordRepRef(child); }
const CordRep* rep;
};
template <Mode mode>
struct RawUsage {
size_t total = 0;
void Add(size_t size, CordRepRef<mode>) { total += size; }
};
template <typename refcount_t>
double MaybeDiv(double d, refcount_t refcount) {
return refcount == 1 ? d : d / refcount;
}
template <>
struct CordRepRef<Mode::kFairShare> {
explicit CordRepRef(const CordRep* r, double frac = 1.0)
: rep(r), fraction(MaybeDiv(frac, r->refcount.Get())) {}
CordRepRef Child(const CordRep* child) const {
return CordRepRef(child, fraction);
}
const CordRep* rep;
double fraction;
};
template <>
struct RawUsage<Mode::kFairShare> {
double total = 0;
void Add(size_t size, CordRepRef<Mode::kFairShare> rep) {
total += static_cast<double>(size) * rep.fraction;
}
};
bool IsDataEdge(const CordRep* rep) {
if (rep->tag == EXTERNAL || rep->tag >= FLAT) return true;
if (rep->tag == SUBSTRING) rep = rep->substring()->child;
return rep->tag == EXTERNAL || rep->tag >= FLAT;
}
template <Mode mode>
void AnalyzeDataEdge(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) {
assert(IsDataEdge(rep.rep));
if (rep.rep->tag == SUBSTRING) {
raw_usage.Add(sizeof(CordRepSubstring), rep);
rep = rep.Child(rep.rep->substring()->child);
}
const size_t size =
rep.rep->tag >= FLAT
? rep.rep->flat()->AllocatedSize()
: rep.rep->length + sizeof(CordRepExternalImpl<intptr_t>);
raw_usage.Add(size, rep);
}
template <Mode mode>
void AnalyzeConcat(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) {
absl::InlinedVector<CordRepRef<mode>, 47> pending;
while (rep.rep != nullptr) {
const CordRepConcat* concat = rep.rep->concat();
CordRepRef<mode> left = rep.Child(concat->left);
CordRepRef<mode> right = rep.Child(concat->right);
raw_usage.Add(sizeof(CordRepConcat), rep);
switch ((IsDataEdge(left.rep) ? 1 : 0) | (IsDataEdge(right.rep) ? 2 : 0)) {
case 0:
rep = left;
pending.push_back(right);
break;
case 1:
AnalyzeDataEdge(left, raw_usage);
rep = right;
break;
case 2:
AnalyzeDataEdge(right, raw_usage);
rep = left;
break;
case 3:
AnalyzeDataEdge(right, raw_usage);
AnalyzeDataEdge(left, raw_usage);
if (!pending.empty()) {
rep = pending.back();
pending.pop_back();
} else {
rep.rep = nullptr;
}
break;
}
}
}
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);
});
}
template <Mode mode>
void AnalyzeBtree(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) {
raw_usage.Add(sizeof(CordRepBtree), rep);
const CordRepBtree* tree = rep.rep->btree();
if (tree->height() > 0) {
for (CordRep* edge : tree->Edges()) {
AnalyzeBtree(rep.Child(edge), raw_usage);
}
} else {
for (CordRep* edge : tree->Edges()) {
AnalyzeDataEdge(rep.Child(edge), raw_usage);
}
}
}
template <Mode mode>
size_t GetEstimatedUsage(const CordRep* rep) {
RawUsage<mode> raw_usage;
CordRepRef<mode> repref(rep);
if (repref.rep->tag == CRC) {
raw_usage.Add(sizeof(CordRepCrc), repref);
repref = repref.Child(repref.rep->crc()->child);
}
if (IsDataEdge(repref.rep)) {
AnalyzeDataEdge(repref, raw_usage);
} else if (repref.rep->tag == BTREE) {
AnalyzeBtree(repref, raw_usage);
} else if (repref.rep->tag == CONCAT) {
AnalyzeConcat(repref, raw_usage);
} else if (repref.rep->tag == RING) {
AnalyzeRing(repref, raw_usage);
} else {
assert(false);
}
return static_cast<size_t>(raw_usage.total);
}
}
size_t GetEstimatedMemoryUsage(const CordRep* rep) {
return GetEstimatedUsage<Mode::kTotal>(rep);
}
size_t GetEstimatedFairShareMemoryUsage(const CordRep* rep) {
return GetEstimatedUsage<Mode::kFairShare>(rep);
}
}
ABSL_NAMESPACE_END
}