Commit 7609a2fb authored by Hannes Roth's avatar Hannes Roth Committed by Tudor Bosman

(Folly/FBString) Enum class for category

Summary: Suggested by @aalexandre.

Test Plan: Compile.

Reviewed By: andrei.alexandrescu@fb.com

Subscribers: folly@lists, njormrod, aalexandre

FB internal diff: D1246180
parent 38fb7acd
...@@ -293,13 +293,13 @@ public: ...@@ -293,13 +293,13 @@ public:
ml_.capacity_ = maxSmallSize << (8 * (sizeof(size_t) - sizeof(Char))); ml_.capacity_ = maxSmallSize << (8 * (sizeof(size_t) - sizeof(Char)));
// or: setSmallSize(0); // or: setSmallSize(0);
writeTerminator(); writeTerminator();
assert(category() == isSmall && size() == 0); assert(category() == Category::isSmall && size() == 0);
} }
fbstring_core(const fbstring_core & rhs) { fbstring_core(const fbstring_core & rhs) {
assert(&rhs != this); assert(&rhs != this);
// Simplest case first: small strings are bitblitted // Simplest case first: small strings are bitblitted
if (rhs.category() == isSmall) { if (rhs.category() == Category::isSmall) {
static_assert(offsetof(MediumLarge, data_) == 0, static_assert(offsetof(MediumLarge, data_) == 0,
"fbstring layout failure"); "fbstring layout failure");
static_assert(offsetof(MediumLarge, size_) == sizeof(ml_.data_), static_assert(offsetof(MediumLarge, size_) == sizeof(ml_.data_),
...@@ -318,12 +318,12 @@ public: ...@@ -318,12 +318,12 @@ public:
// ml_.capacity field). // ml_.capacity field).
ml_ = rhs.ml_; ml_ = rhs.ml_;
} }
assert(category() == isSmall && this->size() == rhs.size()); assert(category() == Category::isSmall && this->size() == rhs.size());
} else if (rhs.category() == isLarge) { } else if (rhs.category() == Category::isLarge) {
// Large strings are just refcounted // Large strings are just refcounted
ml_ = rhs.ml_; ml_ = rhs.ml_;
RefCounted::incrementRefs(ml_.data_); RefCounted::incrementRefs(ml_.data_);
assert(category() == isLarge && size() == rhs.size()); assert(category() == Category::isLarge && size() == rhs.size());
} else { } else {
// Medium strings are copied eagerly. Don't forget to allocate // Medium strings are copied eagerly. Don't forget to allocate
// one extra Char for the null terminator. // one extra Char for the null terminator.
...@@ -337,15 +337,16 @@ public: ...@@ -337,15 +337,16 @@ public:
// No need for writeTerminator() here, we copied one extra // No need for writeTerminator() here, we copied one extra
// element just above. // element just above.
ml_.size_ = rhs.ml_.size_; ml_.size_ = rhs.ml_.size_;
ml_.capacity_ = (allocSize / sizeof(Char) - 1) | isMedium; ml_.capacity_ = (allocSize / sizeof(Char) - 1)
assert(category() == isMedium); | static_cast<category_type>(Category::isMedium);
assert(category() == Category::isMedium);
} }
assert(size() == rhs.size()); assert(size() == rhs.size());
assert(memcmp(data(), rhs.data(), size() * sizeof(Char)) == 0); assert(memcmp(data(), rhs.data(), size() * sizeof(Char)) == 0);
} }
fbstring_core(fbstring_core&& goner) noexcept { fbstring_core(fbstring_core&& goner) noexcept {
if (goner.category() == isSmall) { if (goner.category() == Category::isSmall) {
// Just copy, leave the goner in peace // Just copy, leave the goner in peace
new(this) fbstring_core(goner.small_, goner.smallSize()); new(this) fbstring_core(goner.small_, goner.smallSize());
} else { } else {
...@@ -412,24 +413,26 @@ public: ...@@ -412,24 +413,26 @@ public:
ml_.data_ = static_cast<Char*>(checkedMalloc(allocSize)); ml_.data_ = static_cast<Char*>(checkedMalloc(allocSize));
fbstring_detail::pod_copy(data, data + size, ml_.data_); fbstring_detail::pod_copy(data, data + size, ml_.data_);
ml_.size_ = size; ml_.size_ = size;
ml_.capacity_ = (allocSize / sizeof(Char) - 1) | isMedium; ml_.capacity_ = (allocSize / sizeof(Char) - 1)
| static_cast<category_type>(Category::isMedium);
} else { } else {
// Large strings are allocated differently // Large strings are allocated differently
size_t effectiveCapacity = size; size_t effectiveCapacity = size;
auto const newRC = RefCounted::create(data, & effectiveCapacity); auto const newRC = RefCounted::create(data, & effectiveCapacity);
ml_.data_ = newRC->data_; ml_.data_ = newRC->data_;
ml_.size_ = size; ml_.size_ = size;
ml_.capacity_ = effectiveCapacity | isLarge; ml_.capacity_ = effectiveCapacity
| static_cast<category_type>(Category::isLarge);
} }
writeTerminator(); writeTerminator();
} }
~fbstring_core() noexcept { ~fbstring_core() noexcept {
auto const c = category(); auto const c = category();
if (c == isSmall) { if (c == Category::isSmall) {
return; return;
} }
if (c == isMedium) { if (c == Category::isMedium) {
free(ml_.data_); free(ml_.data_);
return; return;
} }
...@@ -454,7 +457,8 @@ public: ...@@ -454,7 +457,8 @@ public:
ml_.data_ = data; ml_.data_ = data;
ml_.size_ = size; ml_.size_ = size;
// Don't forget about null terminator // Don't forget about null terminator
ml_.capacity_ = (allocatedSize - 1) | isMedium; ml_.capacity_ = (allocatedSize - 1)
| static_cast<category_type>(Category::isMedium);
} else { } else {
// No need for the memory // No need for the memory
free(data); free(data);
...@@ -479,11 +483,11 @@ public: ...@@ -479,11 +483,11 @@ public:
Char * mutable_data() { Char * mutable_data() {
auto const c = category(); auto const c = category();
if (c == isSmall) { if (c == Category::isSmall) {
return small_; return small_;
} }
assert(c == isMedium || c == isLarge); assert(c == Category::isMedium || c == Category::isLarge);
if (c == isLarge && RefCounted::refs(ml_.data_) > 1) { if (c == Category::isLarge && RefCounted::refs(ml_.data_) > 1) {
// Ensure unique. // Ensure unique.
size_t effectiveCapacity = ml_.capacity(); size_t effectiveCapacity = ml_.capacity();
auto const newRC = RefCounted::create(& effectiveCapacity); auto const newRC = RefCounted::create(& effectiveCapacity);
...@@ -501,21 +505,22 @@ public: ...@@ -501,21 +505,22 @@ public:
const Char * c_str() const { const Char * c_str() const {
auto const c = category(); auto const c = category();
if (c == isSmall) { if (c == Category::isSmall) {
assert(small_[smallSize()] == '\0'); assert(small_[smallSize()] == '\0');
return small_; return small_;
} }
assert(c == isMedium || c == isLarge); assert(c == Category::isMedium || c == Category::isLarge);
assert(ml_.data_[ml_.size_] == '\0'); assert(ml_.data_[ml_.size_] == '\0');
return ml_.data_; return ml_.data_;
} }
void shrink(const size_t delta) { void shrink(const size_t delta) {
if (category() == isSmall) { if (category() == Category::isSmall) {
// Check for underflow // Check for underflow
assert(delta <= smallSize()); assert(delta <= smallSize());
setSmallSize(smallSize() - delta); setSmallSize(smallSize() - delta);
} else if (category() == isMedium || RefCounted::refs(ml_.data_) == 1) { } else if (category() == Category::isMedium ||
RefCounted::refs(ml_.data_) == 1) {
// Medium strings and unique large strings need no special // Medium strings and unique large strings need no special
// handling. // handling.
assert(ml_.size_ >= delta); assert(ml_.size_ >= delta);
...@@ -534,7 +539,7 @@ public: ...@@ -534,7 +539,7 @@ public:
} }
void reserve(size_t minCapacity) { void reserve(size_t minCapacity) {
if (category() == isLarge) { if (category() == Category::isLarge) {
// Ensure unique // Ensure unique
if (RefCounted::refs(ml_.data_) > 1) { if (RefCounted::refs(ml_.data_) > 1) {
// We must make it unique regardless; in-place reallocation is // We must make it unique regardless; in-place reallocation is
...@@ -550,7 +555,8 @@ public: ...@@ -550,7 +555,8 @@ public:
// we have + 1 above. // we have + 1 above.
RefCounted::decrementRefs(ml_.data_); RefCounted::decrementRefs(ml_.data_);
ml_.data_ = newRC->data_; ml_.data_ = newRC->data_;
ml_.capacity_ = minCapacity | isLarge; ml_.capacity_ = minCapacity
| static_cast<category_type>(Category::isLarge);
// size remains unchanged // size remains unchanged
} else { } else {
// String is not shared, so let's try to realloc (if needed) // String is not shared, so let's try to realloc (if needed)
...@@ -560,12 +566,13 @@ public: ...@@ -560,12 +566,13 @@ public:
RefCounted::reallocate(ml_.data_, ml_.size_, RefCounted::reallocate(ml_.data_, ml_.size_,
ml_.capacity(), minCapacity); ml_.capacity(), minCapacity);
ml_.data_ = newRC->data_; ml_.data_ = newRC->data_;
ml_.capacity_ = minCapacity | isLarge; ml_.capacity_ = minCapacity
| static_cast<category_type>(Category::isLarge);
writeTerminator(); writeTerminator();
} }
assert(capacity() >= minCapacity); assert(capacity() >= minCapacity);
} }
} else if (category() == isMedium) { } else if (category() == Category::isMedium) {
// String is not shared // String is not shared
if (minCapacity <= ml_.capacity()) { if (minCapacity <= ml_.capacity()) {
return; // nothing to do, there's enough room return; // nothing to do, there's enough room
...@@ -581,7 +588,8 @@ public: ...@@ -581,7 +588,8 @@ public:
(ml_.capacity() + 1) * sizeof(Char), (ml_.capacity() + 1) * sizeof(Char),
capacityBytes)); capacityBytes));
writeTerminator(); writeTerminator();
ml_.capacity_ = (capacityBytes / sizeof(Char) - 1) | isMedium; ml_.capacity_ = (capacityBytes / sizeof(Char) - 1)
| static_cast<category_type>(Category::isMedium);
} else { } else {
// Conversion from medium to large string // Conversion from medium to large string
fbstring_core nascent; fbstring_core nascent;
...@@ -595,7 +603,7 @@ public: ...@@ -595,7 +603,7 @@ public:
assert(capacity() >= minCapacity); assert(capacity() >= minCapacity);
} }
} else { } else {
assert(category() == isSmall); assert(category() == Category::isSmall);
if (minCapacity > maxMediumSize) { if (minCapacity > maxMediumSize) {
// large // large
auto const newRC = RefCounted::create(& minCapacity); auto const newRC = RefCounted::create(& minCapacity);
...@@ -604,7 +612,8 @@ public: ...@@ -604,7 +612,8 @@ public:
// No need for writeTerminator(), we wrote it above with + 1. // No need for writeTerminator(), we wrote it above with + 1.
ml_.data_ = newRC->data_; ml_.data_ = newRC->data_;
ml_.size_ = size; ml_.size_ = size;
ml_.capacity_ = minCapacity | isLarge; ml_.capacity_ = minCapacity
| static_cast<category_type>(Category::isLarge);
assert(capacity() >= minCapacity); assert(capacity() >= minCapacity);
} else if (minCapacity > maxSmallSize) { } else if (minCapacity > maxSmallSize) {
// medium // medium
...@@ -617,7 +626,8 @@ public: ...@@ -617,7 +626,8 @@ public:
// No need for writeTerminator(), we wrote it above with + 1. // No need for writeTerminator(), we wrote it above with + 1.
ml_.data_ = data; ml_.data_ = data;
ml_.size_ = size; ml_.size_ = size;
ml_.capacity_ = (allocSizeBytes / sizeof(Char) - 1) | isMedium; ml_.capacity_ = (allocSizeBytes / sizeof(Char) - 1)
| static_cast<category_type>(Category::isMedium);
} else { } else {
// small // small
// Nothing to do, everything stays put // Nothing to do, everything stays put
...@@ -630,7 +640,7 @@ public: ...@@ -630,7 +640,7 @@ public:
// Strategy is simple: make room, then change size // Strategy is simple: make room, then change size
assert(capacity() >= size()); assert(capacity() >= size());
size_t sz, newSz; size_t sz, newSz;
if (category() == isSmall) { if (category() == Category::isSmall) {
sz = smallSize(); sz = smallSize();
newSz = sz + delta; newSz = sz + delta;
if (newSz <= maxSmallSize) { if (newSz <= maxSmallSize) {
...@@ -647,7 +657,7 @@ public: ...@@ -647,7 +657,7 @@ public:
} }
assert(capacity() >= newSz); assert(capacity() >= newSz);
// Category can't be small - we took care of that above // Category can't be small - we took care of that above
assert(category() == isMedium || category() == isLarge); assert(category() == Category::isMedium || category() == Category::isLarge);
ml_.size_ = newSz; ml_.size_ = newSz;
writeTerminator(); writeTerminator();
assert(size() == newSz); assert(size() == newSz);
...@@ -657,7 +667,7 @@ public: ...@@ -657,7 +667,7 @@ public:
void push_back(Char c) { void push_back(Char c) {
assert(capacity() >= size()); assert(capacity() >= size());
size_t sz; size_t sz;
if (category() == isSmall) { if (category() == Category::isSmall) {
sz = smallSize(); sz = smallSize();
if (sz < maxSmallSize) { if (sz < maxSmallSize) {
small_[sz] = c; small_[sz] = c;
...@@ -674,21 +684,21 @@ public: ...@@ -674,21 +684,21 @@ public:
assert(!isShared()); assert(!isShared());
assert(capacity() >= sz + 1); assert(capacity() >= sz + 1);
// Category can't be small - we took care of that above // Category can't be small - we took care of that above
assert(category() == isMedium || category() == isLarge); assert(category() == Category::isMedium || category() == Category::isLarge);
ml_.size_ = sz + 1; ml_.size_ = sz + 1;
ml_.data_[sz] = c; ml_.data_[sz] = c;
writeTerminator(); writeTerminator();
} }
size_t size() const { size_t size() const {
return category() == isSmall ? smallSize() : ml_.size_; return category() == Category::isSmall ? smallSize() : ml_.size_;
} }
size_t capacity() const { size_t capacity() const {
switch (category()) { switch (category()) {
case isSmall: case Category::isSmall:
return maxSmallSize; return maxSmallSize;
case isLarge: case Category::isLarge:
// For large-sized strings, a multi-referenced chunk has no // For large-sized strings, a multi-referenced chunk has no
// available capacity. This is because any attempt to append // available capacity. This is because any attempt to append
// data would trigger a new allocation. // data would trigger a new allocation.
...@@ -699,11 +709,11 @@ public: ...@@ -699,11 +709,11 @@ public:
} }
bool isShared() const { bool isShared() const {
return category() == isLarge && RefCounted::refs(ml_.data_) > 1; return category() == Category::isLarge && RefCounted::refs(ml_.data_) > 1;
} }
void writeTerminator() { void writeTerminator() {
if (category() == isSmall) { if (category() == Category::isSmall) {
const auto s = smallSize(); const auto s = smallSize();
if (s != maxSmallSize) { if (s != maxSmallSize) {
small_[s] = '\0'; small_[s] = '\0';
...@@ -810,7 +820,10 @@ private: ...@@ -810,7 +820,10 @@ private:
static_assert(!(sizeof(MediumLarge) % sizeof(Char)), static_assert(!(sizeof(MediumLarge) % sizeof(Char)),
"Corrupt memory layout for fbstring."); "Corrupt memory layout for fbstring.");
enum Category { typedef std::conditional<sizeof(size_t) == 4, uint32_t, uint64_t>::type
category_type;
enum class Category : category_type {
isSmall = 0, isSmall = 0,
isMedium = sizeof(size_t) == 4 ? 0x80000000 : 0x8000000000000000, isMedium = sizeof(size_t) == 4 ? 0x80000000 : 0x8000000000000000,
isLarge = sizeof(size_t) == 4 ? 0x40000000 : 0x4000000000000000, isLarge = sizeof(size_t) == 4 ? 0x40000000 : 0x4000000000000000,
...@@ -822,7 +835,7 @@ private: ...@@ -822,7 +835,7 @@ private:
} }
size_t smallSize() const { size_t smallSize() const {
assert(category() == isSmall && assert(category() == Category::isSmall &&
static_cast<size_t>(small_[maxSmallSize]) static_cast<size_t>(small_[maxSmallSize])
<= static_cast<size_t>(maxSmallSize)); <= static_cast<size_t>(maxSmallSize));
return static_cast<size_t>(maxSmallSize) return static_cast<size_t>(maxSmallSize)
......
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