Commit 6c2044f2 authored by Christopher Cole's avatar Christopher Cole Committed by facebook-github-bot-4

Port fbstring_core to big-endian architectures.

Summary: There's 2 ways this could be implemented - either as a series of preprocessor blocks depending on target architecture (as I have implemented it here), or by encapsulating access to MediumLarge::capacity_ within getters/setters as in a similar manner to setSmallSize() and smallSize(). The first option makes the code a bit harder to read, but the second option changes the existing control flow a bit which could slightly alter performance.

I opted for the first so as to keep the existing amd64 flow untouched, but can easily change the pull request to the second option to keep code readability a priority.
Closes https://github.com/facebook/folly/pull/244

Reviewed By: @Gownta

Differential Revision: D2306568

Pulled By: @JoelMarcey
parent 020e1260
...@@ -266,8 +266,8 @@ private: ...@@ -266,8 +266,8 @@ private:
/** /**
* This is the core of the string. The code should work on 32- and * This is the core of the string. The code should work on 32- and
* 64-bit architectures and with any Char size. Porting to big endian * 64-bit and both big- and little-endianan architectures with any
* architectures would require some changes. * Char size.
* *
* The storage is selected as follows (assuming we store one-byte * The storage is selected as follows (assuming we store one-byte
* characters on a 64-bit machine): (a) "small" strings between 0 and * characters on a 64-bit machine): (a) "small" strings between 0 and
...@@ -279,19 +279,29 @@ private: ...@@ -279,19 +279,29 @@ private:
* reference-counted and copied lazily. the reference count is * reference-counted and copied lazily. the reference count is
* allocated right before the character array. * allocated right before the character array.
* *
* The discriminator between these three strategies sits in the two * The discriminator between these three strategies sits in two
* most significant bits of the rightmost char of the storage. If * bits of the rightmost char of the storage. If neither is set, then the
* neither is set, then the string is small (and its length sits in * string is small (and its length sits in the lower-order bits on
* the lower-order bits of that rightmost character). If the MSb is * little-endian or the high-order bits on big-endian of that
* set, the string is medium width. If the second MSb is set, then the * rightmost character). If the MSb is set, the string is medium width.
* string is large. * If the second MSb is set, then the string is large. On little-endian,
* these 2 bits are the 2 MSbs of MediumLarge::capacity_, while on
* big-endian, these 2 bits are the 2 LSbs. This keeps both little-endian
* and big-endian fbstring_core equivalent with merely different ops used
* to extract capacity/category.
*/ */
template <class Char> class fbstring_core { template <class Char> class fbstring_core {
public: public:
fbstring_core() noexcept { fbstring_core() noexcept {
// Only initialize the tag, will set the MSBs (i.e. the small // Only initialize the tag, will set the MSBs (i.e. the small
// string size) to zero too // string size) to zero too
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
ml_.capacity_ = maxSmallSize << (8 * (sizeof(size_t) - sizeof(Char))); ml_.capacity_ = maxSmallSize << (8 * (sizeof(size_t) - sizeof(Char)));
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
ml_.capacity_ = maxSmallSize << 2;
#else
#error Unable to identify target endianness
#endif
// or: setSmallSize(0); // or: setSmallSize(0);
writeTerminator(); writeTerminator();
assert(category() == Category::isSmall && size() == 0); assert(category() == Category::isSmall && size() == 0);
...@@ -338,8 +348,7 @@ public: ...@@ -338,8 +348,7 @@ 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) ml_.setCapacity(allocSize / sizeof(Char) - 1, Category::isMedium);
| static_cast<category_type>(Category::isMedium);
assert(category() == Category::isMedium); assert(category() == Category::isMedium);
} }
assert(size() == rhs.size()); assert(size() == rhs.size());
...@@ -414,16 +423,14 @@ public: ...@@ -414,16 +423,14 @@ 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) ml_.setCapacity(allocSize / sizeof(Char) - 1, Category::isMedium);
| 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 ml_.setCapacity(effectiveCapacity, Category::isLarge);
| static_cast<category_type>(Category::isLarge);
} }
writeTerminator(); writeTerminator();
} }
...@@ -458,8 +465,7 @@ public: ...@@ -458,8 +465,7 @@ 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) ml_.setCapacity(allocatedSize - 1, Category::isMedium);
| static_cast<category_type>(Category::isMedium);
} else { } else {
// No need for the memory // No need for the memory
free(data); free(data);
...@@ -556,8 +562,7 @@ public: ...@@ -556,8 +562,7 @@ 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 ml_.setCapacity(minCapacity, Category::isLarge);
| 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)
...@@ -567,8 +572,7 @@ public: ...@@ -567,8 +572,7 @@ 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 ml_.setCapacity(minCapacity, Category::isLarge);
| static_cast<category_type>(Category::isLarge);
writeTerminator(); writeTerminator();
} }
assert(capacity() >= minCapacity); assert(capacity() >= minCapacity);
...@@ -589,8 +593,7 @@ public: ...@@ -589,8 +593,7 @@ public:
(ml_.capacity() + 1) * sizeof(Char), (ml_.capacity() + 1) * sizeof(Char),
capacityBytes)); capacityBytes));
writeTerminator(); writeTerminator();
ml_.capacity_ = (capacityBytes / sizeof(Char) - 1) ml_.setCapacity(capacityBytes / sizeof(Char) - 1, Category::isMedium);
| 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;
...@@ -613,8 +616,7 @@ public: ...@@ -613,8 +616,7 @@ 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 ml_.setCapacity(minCapacity, Category::isLarge);
| static_cast<category_type>(Category::isLarge);
assert(capacity() >= minCapacity); assert(capacity() >= minCapacity);
} else if (minCapacity > maxSmallSize) { } else if (minCapacity > maxSmallSize) {
// medium // medium
...@@ -627,8 +629,7 @@ public: ...@@ -627,8 +629,7 @@ 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) ml_.setCapacity(allocSizeBytes / sizeof(Char) - 1, Category::isMedium);
| static_cast<category_type>(Category::isMedium);
} else { } else {
// small // small
// Nothing to do, everything stays put // Nothing to do, everything stays put
...@@ -728,16 +729,6 @@ private: ...@@ -728,16 +729,6 @@ private:
// Disabled // Disabled
fbstring_core & operator=(const fbstring_core & rhs); fbstring_core & operator=(const fbstring_core & rhs);
struct MediumLarge {
Char * data_;
size_t size_;
size_t capacity_;
size_t capacity() const {
return capacity_ & capacityExtractMask;
}
};
struct RefCounted { struct RefCounted {
std::atomic<size_t> refCount_; std::atomic<size_t> refCount_;
Char data_[1]; Char data_[1];
...@@ -805,6 +796,53 @@ private: ...@@ -805,6 +796,53 @@ private:
} }
}; };
typedef std::conditional<sizeof(size_t) == 4, uint32_t, uint64_t>::type
category_type;
enum class Category : category_type {
isSmall = 0,
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
isMedium = sizeof(size_t) == 4 ? 0x80000000 : 0x8000000000000000,
isLarge = sizeof(size_t) == 4 ? 0x40000000 : 0x4000000000000000,
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
isMedium = 0x2,
isLarge = 0x1,
#else
#error Unable to identify target endianness
#endif
};
Category category() const {
// works for both big-endian and little-endian
return static_cast<Category>(ml_.capacity_ & categoryExtractMask);
}
struct MediumLarge {
Char * data_;
size_t size_;
size_t capacity_;
size_t capacity() const {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return capacity_ & capacityExtractMask;
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
return capacity_ >> 2;
#else
#error Unable to identify target endianness
#endif
}
void setCapacity(size_t cap, Category cat) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
capacity_ = cap | static_cast<category_type>(cat);
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
capacity_ = (cap << 2) | static_cast<category_type>(cat);
#else
#error Unable to identify target endianness
#endif
}
};
union { union {
Char small_[sizeof(MediumLarge) / sizeof(Char)]; Char small_[sizeof(MediumLarge) / sizeof(Char)];
MediumLarge ml_; MediumLarge ml_;
...@@ -815,32 +853,34 @@ private: ...@@ -815,32 +853,34 @@ private:
maxSmallSize = lastChar / sizeof(Char), maxSmallSize = lastChar / sizeof(Char),
maxMediumSize = 254 / sizeof(Char), // coincides with the small maxMediumSize = 254 / sizeof(Char), // coincides with the small
// bin size in dlmalloc // bin size in dlmalloc
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
categoryExtractMask = sizeof(size_t) == 4 ? 0xC0000000 : 0xC000000000000000, categoryExtractMask = sizeof(size_t) == 4 ? 0xC0000000 : 0xC000000000000000,
capacityExtractMask = ~categoryExtractMask, capacityExtractMask = ~categoryExtractMask,
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
categoryExtractMask = 0x3,
#else
#error Unable to identify target endianness
#endif
}; };
static_assert(!(sizeof(MediumLarge) % sizeof(Char)), static_assert(!(sizeof(MediumLarge) % sizeof(Char)),
"Corrupt memory layout for fbstring."); "Corrupt memory layout for fbstring.");
typedef std::conditional<sizeof(size_t) == 4, uint32_t, uint64_t>::type
category_type;
enum class Category : category_type {
isSmall = 0,
isMedium = sizeof(size_t) == 4 ? 0x80000000 : 0x8000000000000000,
isLarge = sizeof(size_t) == 4 ? 0x40000000 : 0x4000000000000000,
};
Category category() const {
// Assumes little endian
return static_cast<Category>(ml_.capacity_ & categoryExtractMask);
}
size_t smallSize() const { size_t smallSize() const {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
assert(category() == 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)
- static_cast<size_t>(small_[maxSmallSize]); - static_cast<size_t>(small_[maxSmallSize]);
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
assert(category() == Category::isSmall &&
(static_cast<size_t>(small_[maxSmallSize]) >> 2)
<= static_cast<size_t>(maxSmallSize));
return static_cast<size_t>(maxSmallSize)
- (static_cast<size_t>(small_[maxSmallSize]) >> 2);
#else
#error Unable to identify target endianness
#endif
} }
void setSmallSize(size_t s) { void setSmallSize(size_t s) {
...@@ -848,7 +888,13 @@ private: ...@@ -848,7 +888,13 @@ private:
// so don't assume anything about the previous value of // so don't assume anything about the previous value of
// small_[maxSmallSize]. // small_[maxSmallSize].
assert(s <= maxSmallSize); assert(s <= maxSmallSize);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
small_[maxSmallSize] = maxSmallSize - s; small_[maxSmallSize] = maxSmallSize - s;
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
small_[maxSmallSize] = (maxSmallSize - s) << 2;
#else
#error Unable to identify target endianness
#endif
writeTerminator(); writeTerminator();
} }
}; };
......
...@@ -9,8 +9,8 @@ allocator. In particular, `fbstring` is designed to detect use of ...@@ -9,8 +9,8 @@ allocator. In particular, `fbstring` is designed to detect use of
jemalloc and cooperate with it to achieve significant improvements in jemalloc and cooperate with it to achieve significant improvements in
speed and memory usage. speed and memory usage.
`fbstring` supports x32 and x64 architectures. Porting it to big endian `fbstring` supports 32- and 64-bit and little- and big-endian
architectures would require some changes. architectures.
### Storage strategies ### Storage strategies
*** ***
......
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