X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2FFBString.h;h=f5b577a777d61cb939ae66e89c9475fe7222fe1e;hb=6b1a43fd0712c15cb98f134bc2d8c89713b24513;hp=7a9d6edee9c197a708a664529240ebb24ecc747e;hpb=dbd70eed51804682cf2d6875bcbe580771688588;p=folly.git diff --git a/folly/FBString.h b/folly/FBString.h index 7a9d6ede..f5b577a7 100644 --- a/folly/FBString.h +++ b/folly/FBString.h @@ -1,5 +1,5 @@ /* - * Copyright 2016 Facebook, Inc. + * Copyright 2017 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ #pragma once #include +#include +#include #include #include @@ -31,14 +33,11 @@ #pragma GCC system_header -// When used as std::string replacement always disable assertions. -#ifndef NDEBUG -#define NDEBUG -#define FOLLY_DEFINED_NDEBUG_FOR_FBSTRING -#endif // NDEBUG - #include "basic_fbstring_malloc.h" +// When used as std::string replacement always disable assertions. +#define FBSTRING_ASSERT(expr) /* empty */ + #else // !_LIBSTDCXX_FBSTRING #include @@ -65,6 +64,9 @@ #endif #endif +// When used in folly, assertions are not disabled. +#define FBSTRING_ASSERT(expr) assert(expr) + #endif // We defined these here rather than including Likely.h to avoid @@ -134,9 +136,10 @@ inline std::pair copy_n( template inline void podFill(Pod* b, Pod* e, T c) { - assert(b && e && b <= e); - /*static*/ if (sizeof(T) == 1) { - memset(b, c, e - b); + FBSTRING_ASSERT(b && e && b <= e); + constexpr auto kUseMemset = sizeof(T) == 1; + /* static */ if (kUseMemset) { + memset(b, c, size_t(e - b)); } else { auto const ee = b + ((e - b) & ~7u); for (; b != ee; b += 8) { @@ -166,8 +169,11 @@ inline void podFill(Pod* b, Pod* e, T c) { */ template inline void podCopy(const Pod* b, const Pod* e, Pod* d) { - assert(e >= b); - assert(d >= e || d + (e - b) <= b); + FBSTRING_ASSERT(b != nullptr); + FBSTRING_ASSERT(e != nullptr); + FBSTRING_ASSERT(d != nullptr); + FBSTRING_ASSERT(e >= b); + FBSTRING_ASSERT(d >= e || d + (e - b) <= b); memcpy(d, b, (e - b) * sizeof(Pod)); } @@ -177,7 +183,7 @@ inline void podCopy(const Pod* b, const Pod* e, Pod* d) { */ template inline void podMove(const Pod* b, const Pod* e, Pod* d) { - assert(e >= b); + FBSTRING_ASSERT(e >= b); memmove(d, b, (e - b) * sizeof(*b)); } @@ -321,7 +327,7 @@ public: fbstring_core() noexcept { reset(); } fbstring_core(const fbstring_core & rhs) { - assert(&rhs != this); + FBSTRING_ASSERT(&rhs != this); switch (rhs.category()) { case Category::isSmall: copySmall(rhs); @@ -335,8 +341,8 @@ public: default: fbstring_detail::assume_unreachable(); } - assert(size() == rhs.size()); - assert(memcmp(data(), rhs.data(), size() * sizeof(Char)) == 0); + FBSTRING_ASSERT(size() == rhs.size()); + FBSTRING_ASSERT(memcmp(data(), rhs.data(), size() * sizeof(Char)) == 0); } fbstring_core(fbstring_core&& goner) noexcept { @@ -356,24 +362,16 @@ public: } else { initLarge(data, size); } -#ifndef NDEBUG -#ifndef _LIBSTDCXX_FBSTRING - assert(this->size() == size); - assert(size == 0 || memcmp(this->data(), data, size * sizeof(Char)) == 0); -#endif -#endif + FBSTRING_ASSERT(this->size() == size); + FBSTRING_ASSERT( + size == 0 || memcmp(this->data(), data, size * sizeof(Char)) == 0); } ~fbstring_core() noexcept { - auto const c = category(); - if (c == Category::isSmall) { - return; - } - if (c == Category::isMedium) { - free(ml_.data_); + if (category() == Category::isSmall) { return; } - RefCounted::decrementRefs(ml_.data_); + destroyMediumLarge(); } // Snatches a previously mallocated string. The parameter "size" @@ -388,8 +386,8 @@ public: const size_t allocatedSize, AcquireMallocatedString) { if (size > 0) { - assert(allocatedSize >= size + 1); - assert(data[size] == '\0'); + FBSTRING_ASSERT(allocatedSize >= size + 1); + FBSTRING_ASSERT(data[size] == '\0'); // Use the medium string storage ml_.data_ = data; ml_.size_ = size; @@ -429,15 +427,11 @@ public: fbstring_detail::assume_unreachable(); } - const Char * c_str() const { - auto const c = category(); - if (c == Category::isSmall) { - assert(small_[smallSize()] == '\0'); - return small_; - } - assert(c == Category::isMedium || c == Category::isLarge); - assert(ml_.data_[ml_.size_] == '\0'); - return ml_.data_; + const Char* c_str() const { + const Char* ptr = ml_.data_; + // With this syntax, GCC and Clang generate a CMOV instead of a branch. + ptr = (category() == Category::isSmall) ? small_ : ptr; + return ptr; } void shrink(const size_t delta) { @@ -451,6 +445,7 @@ public: } } + FOLLY_MALLOC_NOINLINE void reserve(size_t minCapacity, bool disableSSO = FBSTRING_DISABLE_SSO) { switch (category()) { case Category::isSmall: @@ -465,7 +460,7 @@ public: default: fbstring_detail::assume_unreachable(); } - assert(capacity() >= minCapacity); + FBSTRING_ASSERT(capacity() >= minCapacity); } Char* expandNoinit( @@ -478,7 +473,19 @@ public: } size_t size() const { - return category() == Category::isSmall ? smallSize() : ml_.size_; + size_t ret = ml_.size_; + /* static */ if (kIsLittleEndian) { + // We can save a couple instructions, because the category is + // small iff the last char, as unsigned, is <= maxSmallSize. + typedef typename std::make_unsigned::type UChar; + auto maybeSmallSize = size_t(maxSmallSize) - + size_t(static_cast(small_[maxSmallSize])); + // With this syntax, GCC and Clang generate a CMOV instead of a branch. + ret = (static_cast(maybeSmallSize) >= 0) ? maybeSmallSize : ret; + } else { + ret = (category() == Category::isSmall) ? smallSize() : ret; + } + return ret; } size_t capacity() const { @@ -505,25 +512,32 @@ private: // Disabled fbstring_core & operator=(const fbstring_core & rhs); - // Equivalent to setSmallSize(0) but a few ns faster in - // microbenchmarks. void reset() { - ml_.capacity_ = kIsLittleEndian - ? maxSmallSize << (8 * (sizeof(size_t) - sizeof(Char))) - : maxSmallSize << 2; - small_[0] = '\0'; - assert(category() == Category::isSmall && size() == 0); + setSmallSize(0); + } + + FOLLY_MALLOC_NOINLINE void destroyMediumLarge() noexcept { + auto const c = category(); + FBSTRING_ASSERT(c != Category::isSmall); + if (c == Category::isMedium) { + free(ml_.data_); + } else { + RefCounted::decrementRefs(ml_.data_); + } } struct RefCounted { std::atomic refCount_; Char data_[1]; + constexpr static size_t getDataOffset() { + return offsetof(RefCounted, data_); + } + static RefCounted * fromData(Char * p) { - return static_cast( - static_cast( - static_cast(static_cast(p)) - - sizeof(refCount_))); + return static_cast(static_cast( + static_cast(static_cast(p)) - + getDataOffset())); } static size_t refs(Char * p) { @@ -537,67 +551,61 @@ private: static void decrementRefs(Char * p) { auto const dis = fromData(p); size_t oldcnt = dis->refCount_.fetch_sub(1, std::memory_order_acq_rel); - assert(oldcnt > 0); + FBSTRING_ASSERT(oldcnt > 0); if (oldcnt == 1) { free(dis); } } static RefCounted * create(size_t * size) { - // Don't forget to allocate one extra Char for the terminating - // null. In this case, however, one Char is already part of the - // struct. - const size_t allocSize = goodMallocSize( - sizeof(RefCounted) + *size * sizeof(Char)); + const size_t allocSize = + goodMallocSize(getDataOffset() + (*size + 1) * sizeof(Char)); auto result = static_cast(checkedMalloc(allocSize)); result->refCount_.store(1, std::memory_order_release); - *size = (allocSize - sizeof(RefCounted)) / sizeof(Char); + *size = (allocSize - getDataOffset()) / sizeof(Char) - 1; return result; } static RefCounted * create(const Char * data, size_t * size) { const size_t effectiveSize = *size; auto result = create(size); - fbstring_detail::podCopy(data, data + effectiveSize, result->data_); + if (FBSTRING_LIKELY(effectiveSize > 0)) { + fbstring_detail::podCopy(data, data + effectiveSize, result->data_); + } return result; } static RefCounted * reallocate(Char *const data, const size_t currentSize, const size_t currentCapacity, - const size_t newCapacity) { - assert(newCapacity > 0 && newCapacity > currentSize); + size_t * newCapacity) { + FBSTRING_ASSERT(*newCapacity > 0 && *newCapacity > currentSize); + const size_t allocNewCapacity = + goodMallocSize(getDataOffset() + (*newCapacity + 1) * sizeof(Char)); auto const dis = fromData(data); - assert(dis->refCount_.load(std::memory_order_acquire) == 1); - // Don't forget to allocate one extra Char for the terminating - // null. In this case, however, one Char is already part of the - // struct. - auto result = static_cast( - smartRealloc(dis, - sizeof(RefCounted) + currentSize * sizeof(Char), - sizeof(RefCounted) + currentCapacity * sizeof(Char), - sizeof(RefCounted) + newCapacity * sizeof(Char))); - assert(result->refCount_.load(std::memory_order_acquire) == 1); + FBSTRING_ASSERT(dis->refCount_.load(std::memory_order_acquire) == 1); + auto result = static_cast(smartRealloc( + dis, + getDataOffset() + (currentSize + 1) * sizeof(Char), + getDataOffset() + (currentCapacity + 1) * sizeof(Char), + allocNewCapacity)); + FBSTRING_ASSERT(result->refCount_.load(std::memory_order_acquire) == 1); + *newCapacity = (allocNewCapacity - getDataOffset()) / sizeof(Char) - 1; return result; } }; - typedef std::conditional::type - category_type; + typedef uint8_t category_type; enum class Category : category_type { isSmall = 0, - isMedium = kIsLittleEndian - ? sizeof(size_t) == 4 ? 0x80000000 : 0x8000000000000000 - : 0x2, - isLarge = kIsLittleEndian - ? sizeof(size_t) == 4 ? 0x40000000 : 0x4000000000000000 - : 0x1, + isMedium = kIsLittleEndian ? 0x80 : 0x2, + isLarge = kIsLittleEndian ? 0x40 : 0x1, }; Category category() const { // works for both big-endian and little-endian - return static_cast(ml_.capacity_ & categoryExtractMask); + return static_cast(bytes_[lastChar] & categoryExtractMask); } struct MediumLarge { @@ -612,37 +620,35 @@ private: } void setCapacity(size_t cap, Category cat) { - capacity_ = kIsLittleEndian - ? cap | static_cast(cat) - : (cap << 2) | static_cast(cat); + capacity_ = kIsLittleEndian + ? cap | (static_cast(cat) << kCategoryShift) + : (cap << 2) | static_cast(cat); } }; union { + uint8_t bytes_[sizeof(MediumLarge)]; // For accessing the last byte. Char small_[sizeof(MediumLarge) / sizeof(Char)]; MediumLarge ml_; }; - enum : size_t { - lastChar = sizeof(MediumLarge) - 1, - maxSmallSize = lastChar / sizeof(Char), - maxMediumSize = 254 / sizeof(Char), // coincides with the small - // bin size in dlmalloc - categoryExtractMask = kIsLittleEndian - ? sizeof(size_t) == 4 ? 0xC0000000 : size_t(0xC000000000000000) - : 0x3, - capacityExtractMask = kIsLittleEndian - ? ~categoryExtractMask - : 0x0 /*unused*/, - }; + constexpr static size_t lastChar = sizeof(MediumLarge) - 1; + constexpr static size_t maxSmallSize = lastChar / sizeof(Char); + constexpr static size_t maxMediumSize = 254 / sizeof(Char); + constexpr static uint8_t categoryExtractMask = kIsLittleEndian ? 0xC0 : 0x3; + constexpr static size_t kCategoryShift = (sizeof(size_t) - 1) * 8; + constexpr static size_t capacityExtractMask = kIsLittleEndian + ? ~(size_t(categoryExtractMask) << kCategoryShift) + : 0x0 /* unused */; + static_assert(!(sizeof(MediumLarge) % sizeof(Char)), "Corrupt memory layout for fbstring."); size_t smallSize() const { - assert(category() == Category::isSmall); + FBSTRING_ASSERT(category() == Category::isSmall); constexpr auto shift = kIsLittleEndian ? 0 : 2; auto smallShifted = static_cast(small_[maxSmallSize]) >> shift; - assert(static_cast(maxSmallSize) >= smallShifted); + FBSTRING_ASSERT(static_cast(maxSmallSize) >= smallShifted); return static_cast(maxSmallSize) - smallShifted; } @@ -650,11 +656,11 @@ private: // Warning: this should work with uninitialized strings too, // so don't assume anything about the previous value of // small_[maxSmallSize]. - assert(s <= maxSmallSize); + FBSTRING_ASSERT(s <= maxSmallSize); constexpr auto shift = kIsLittleEndian ? 0 : 2; - small_[maxSmallSize] = (maxSmallSize - s) << shift; + small_[maxSmallSize] = char((maxSmallSize - s) << shift); small_[s] = '\0'; - assert(category() == Category::isSmall && size() == s); + FBSTRING_ASSERT(category() == Category::isSmall && size() == s); } void copySmall(const fbstring_core&); @@ -673,6 +679,7 @@ private: void shrinkMedium(size_t delta); void shrinkLarge(size_t delta); + void unshare(size_t minCapacity = 0); Char* mutableDataLarge(); }; @@ -691,11 +698,13 @@ inline void fbstring_core::copySmall(const fbstring_core& rhs) { // which stores a short string's length, is shared with the // ml_.capacity field). ml_ = rhs.ml_; - assert(category() == Category::isSmall && this->size() == rhs.size()); + FBSTRING_ASSERT( + category() == Category::isSmall && this->size() == rhs.size()); } template -inline void fbstring_core::copyMedium(const fbstring_core& rhs) { +FOLLY_MALLOC_NOINLINE inline void fbstring_core::copyMedium( + const fbstring_core& rhs) { // Medium strings are copied eagerly. Don't forget to allocate // one extra Char for the null terminator. auto const allocSize = goodMallocSize((1 + rhs.ml_.size_) * sizeof(Char)); @@ -705,15 +714,16 @@ inline void fbstring_core::copyMedium(const fbstring_core& rhs) { rhs.ml_.data_, rhs.ml_.data_ + rhs.ml_.size_ + 1, ml_.data_); ml_.size_ = rhs.ml_.size_; ml_.setCapacity(allocSize / sizeof(Char) - 1, Category::isMedium); - assert(category() == Category::isMedium); + FBSTRING_ASSERT(category() == Category::isMedium); } template -inline void fbstring_core::copyLarge(const fbstring_core& rhs) { +FOLLY_MALLOC_NOINLINE inline void fbstring_core::copyLarge( + const fbstring_core& rhs) { // Large strings are just refcounted ml_ = rhs.ml_; RefCounted::incrementRefs(ml_.data_); - assert(category() == Category::isLarge && size() == rhs.size()); + FBSTRING_ASSERT(category() == Category::isLarge && size() == rhs.size()); } // Small strings are bitblitted @@ -761,20 +771,22 @@ inline void fbstring_core::initSmall( } template -inline void fbstring_core::initMedium( +FOLLY_MALLOC_NOINLINE inline void fbstring_core::initMedium( const Char* const data, const size_t size) { // Medium strings are allocated normally. Don't forget to // allocate one extra Char for the terminating null. auto const allocSize = goodMallocSize((1 + size) * sizeof(Char)); ml_.data_ = static_cast(checkedMalloc(allocSize)); - fbstring_detail::podCopy(data, data + size, ml_.data_); + if (FBSTRING_LIKELY(size > 0)) { + fbstring_detail::podCopy(data, data + size, ml_.data_); + } ml_.size_ = size; ml_.setCapacity(allocSize / sizeof(Char) - 1, Category::isMedium); ml_.data_[size] = '\0'; } template -inline void fbstring_core::initLarge( +FOLLY_MALLOC_NOINLINE inline void fbstring_core::initLarge( const Char* const data, const size_t size) { // Large strings are allocated differently size_t effectiveCapacity = size; @@ -785,60 +797,60 @@ inline void fbstring_core::initLarge( ml_.data_[size] = '\0'; } +template +FOLLY_MALLOC_NOINLINE inline void fbstring_core::unshare( + size_t minCapacity) { + FBSTRING_ASSERT(category() == Category::isLarge); + size_t effectiveCapacity = std::max(minCapacity, ml_.capacity()); + auto const newRC = RefCounted::create(&effectiveCapacity); + // If this fails, someone placed the wrong capacity in an + // fbstring. + FBSTRING_ASSERT(effectiveCapacity >= ml_.capacity()); + // Also copies terminator. + fbstring_detail::podCopy(ml_.data_, ml_.data_ + ml_.size_ + 1, newRC->data_); + RefCounted::decrementRefs(ml_.data_); + ml_.data_ = newRC->data_; + ml_.setCapacity(effectiveCapacity, Category::isLarge); + // size_ remains unchanged. +} + template inline Char* fbstring_core::mutableDataLarge() { - assert(category() == Category::isLarge); - if (RefCounted::refs(ml_.data_) > 1) { - // Ensure unique. - size_t effectiveCapacity = ml_.capacity(); - auto const newRC = RefCounted::create(&effectiveCapacity); - // If this fails, someone placed the wrong capacity in an - // fbstring. - assert(effectiveCapacity >= ml_.capacity()); - // Also copies terminator. - fbstring_detail::podCopy( - ml_.data_, ml_.data_ + ml_.size_ + 1, newRC->data_); - RefCounted::decrementRefs(ml_.data_); - ml_.data_ = newRC->data_; + FBSTRING_ASSERT(category() == Category::isLarge); + if (RefCounted::refs(ml_.data_) > 1) { // Ensure unique. + unshare(); } return ml_.data_; } template -inline void fbstring_core::reserveLarge(size_t minCapacity) { - assert(category() == Category::isLarge); - // Ensure unique - if (RefCounted::refs(ml_.data_) > 1) { +FOLLY_MALLOC_NOINLINE inline void fbstring_core::reserveLarge( + size_t minCapacity) { + FBSTRING_ASSERT(category() == Category::isLarge); + if (RefCounted::refs(ml_.data_) > 1) { // Ensure unique // We must make it unique regardless; in-place reallocation is // useless if the string is shared. In order to not surprise // people, reserve the new block at current capacity or // more. That way, a string's capacity never shrinks after a // call to reserve. - minCapacity = std::max(minCapacity, ml_.capacity()); - auto const newRC = RefCounted::create(&minCapacity); - // Also copies terminator. - fbstring_detail::podCopy( - ml_.data_, ml_.data_ + ml_.size_ + 1, newRC->data_); - RefCounted::decrementRefs(ml_.data_); - ml_.data_ = newRC->data_; - ml_.setCapacity(minCapacity, Category::isLarge); - // size remains unchanged + unshare(minCapacity); } else { // String is not shared, so let's try to realloc (if needed) if (minCapacity > ml_.capacity()) { // Asking for more memory auto const newRC = RefCounted::reallocate( - ml_.data_, ml_.size_, ml_.capacity(), minCapacity); + ml_.data_, ml_.size_, ml_.capacity(), &minCapacity); ml_.data_ = newRC->data_; ml_.setCapacity(minCapacity, Category::isLarge); } - assert(capacity() >= minCapacity); + FBSTRING_ASSERT(capacity() >= minCapacity); } } template -inline void fbstring_core::reserveMedium(const size_t minCapacity) { - assert(category() == Category::isMedium); +FOLLY_MALLOC_NOINLINE inline void fbstring_core::reserveMedium( + const size_t minCapacity) { + FBSTRING_ASSERT(category() == Category::isMedium); // String is not shared if (minCapacity <= ml_.capacity()) { return; // nothing to do, there's enough room @@ -864,14 +876,14 @@ inline void fbstring_core::reserveMedium(const size_t minCapacity) { fbstring_detail::podCopy( ml_.data_, ml_.data_ + ml_.size_ + 1, nascent.ml_.data_); nascent.swap(*this); - assert(capacity() >= minCapacity); + FBSTRING_ASSERT(capacity() >= minCapacity); } } template -inline void fbstring_core::reserveSmall( +FOLLY_MALLOC_NOINLINE inline void fbstring_core::reserveSmall( size_t minCapacity, const bool disableSSO) { - assert(category() == Category::isSmall); + FBSTRING_ASSERT(category() == Category::isSmall); if (!disableSSO && minCapacity <= maxSmallSize) { // small // Nothing to do, everything stays put @@ -896,7 +908,7 @@ inline void fbstring_core::reserveSmall( ml_.data_ = newRC->data_; ml_.size_ = size; ml_.setCapacity(minCapacity, Category::isLarge); - assert(capacity() >= minCapacity); + FBSTRING_ASSERT(capacity() >= minCapacity); } } @@ -906,7 +918,7 @@ inline Char* fbstring_core::expandNoinit( bool expGrowth, /* = false */ bool disableSSO /* = FBSTRING_DISABLE_SSO */) { // Strategy is simple: make room, then change size - assert(capacity() >= size()); + FBSTRING_ASSERT(capacity() >= size()); size_t sz, newSz; if (category() == Category::isSmall) { sz = smallSize(); @@ -925,19 +937,20 @@ inline Char* fbstring_core::expandNoinit( reserve(expGrowth ? std::max(newSz, 1 + capacity() * 3 / 2) : newSz); } } - assert(capacity() >= newSz); + FBSTRING_ASSERT(capacity() >= newSz); // Category can't be small - we took care of that above - assert(category() == Category::isMedium || category() == Category::isLarge); + FBSTRING_ASSERT( + category() == Category::isMedium || category() == Category::isLarge); ml_.size_ = newSz; ml_.data_[newSz] = '\0'; - assert(size() == newSz); + FBSTRING_ASSERT(size() == newSz); return ml_.data_ + sz; } template inline void fbstring_core::shrinkSmall(const size_t delta) { // Check for underflow - assert(delta <= smallSize()); + FBSTRING_ASSERT(delta <= smallSize()); setSmallSize(smallSize() - delta); } @@ -945,14 +958,14 @@ template inline void fbstring_core::shrinkMedium(const size_t delta) { // Medium strings and unique large strings need no special // handling. - assert(ml_.size_ >= delta); + FBSTRING_ASSERT(ml_.size_ >= delta); ml_.size_ -= delta; ml_.data_[ml_.size_] = '\0'; } template inline void fbstring_core::shrinkLarge(const size_t delta) { - assert(ml_.size_ >= delta); + FBSTRING_ASSERT(ml_.size_ >= delta); // Shared large string, must make unique. This is because of the // durn terminator must be written, which may trample the shared // data. @@ -988,7 +1001,7 @@ public: return const_cast(backend_.data()); } void shrink(size_t delta) { - assert(delta <= size()); + FBSTRING_ASSERT(delta <= size()); backend_.resize(size() - delta); } Char* expandNoinit(size_t delta) { @@ -1031,7 +1044,6 @@ template > #endif class basic_fbstring { - static void enforce( bool condition, void (*throw_exc)(const char*), @@ -1052,25 +1064,20 @@ class basic_fbstring { begin()[size()] == '\0'; } - struct Invariant; - friend struct Invariant; struct Invariant { Invariant& operator=(const Invariant&) = delete; -#ifndef NDEBUG - explicit Invariant(const basic_fbstring& s) : s_(s) { - assert(s_.isSane()); + explicit Invariant(const basic_fbstring& s) noexcept : s_(s) { + FBSTRING_ASSERT(s_.isSane()); } - ~Invariant() { - assert(s_.isSane()); + ~Invariant() noexcept { + FBSTRING_ASSERT(s_.isSane()); } - private: + + private: const basic_fbstring& s_; -#else - explicit Invariant(const basic_fbstring&) {} -#endif }; -public: + public: // types typedef T traits_type; typedef typename traits_type::char_type value_type; @@ -1152,21 +1159,23 @@ public: assign(str, pos, n); } + FOLLY_MALLOC_NOINLINE /* implicit */ basic_fbstring(const value_type* s, const A& /*a*/ = A()) - : store_(s, basic_fbstring::traitsLength(s)) { - } + : store_(s, traitsLength(s)) {} + FOLLY_MALLOC_NOINLINE basic_fbstring(const value_type* s, size_type n, const A& /*a*/ = A()) : store_(s, n) { } + FOLLY_MALLOC_NOINLINE basic_fbstring(size_type n, value_type c, const A& /*a*/ = A()) { auto const pData = store_.expandNoinit(n); fbstring_detail::podFill(pData, pData + n, c); } template - basic_fbstring( + FOLLY_MALLOC_NOINLINE basic_fbstring( InIt begin, InIt end, typename std::enable_if< @@ -1176,8 +1185,9 @@ public: } // Specialization for const char*, const char* + FOLLY_MALLOC_NOINLINE basic_fbstring(const value_type* b, const value_type* e, const A& /*a*/ = A()) - : store_(b, e - b) { + : store_(b, size_type(e - b)) { } // Nonstandard constructor @@ -1187,12 +1197,12 @@ public: } // Construction from initialization list + FOLLY_MALLOC_NOINLINE basic_fbstring(std::initializer_list il) { assign(il.begin(), il.end()); } - ~basic_fbstring() noexcept { - } + ~basic_fbstring() noexcept {} basic_fbstring& operator=(const basic_fbstring& lhs); @@ -1220,7 +1230,24 @@ public: return assign(s); } - basic_fbstring& operator=(value_type c); + // This actually goes directly against the C++ spec, but the + // value_type overload is dangerous, so we're explicitly deleting + // any overloads of operator= that could implicitly convert to + // value_type. + // Note that we do need to explicitly specify the template types because + // otherwise MSVC 2017 will aggressively pre-resolve value_type to + // traits_type::char_type, which won't compare as equal when determining + // which overload the implementation is referring to. + // Also note that MSVC 2015 Update 3 requires us to explicitly specify the + // namespace in-which to search for basic_fbstring, otherwise it tries to + // look for basic_fbstring::basic_fbstring, which is just plain wrong. + template + typename std::enable_if< + std::is_same< + typename std::decay::type, + typename folly::basic_fbstring::value_type>::value, + basic_fbstring&>::type + operator=(TP c); basic_fbstring& operator=(std::initializer_list il) { return assign(il.begin(), il.end()); @@ -1273,18 +1300,18 @@ public: // C++11 21.4.5, element access: const value_type& front() const { return *begin(); } const value_type& back() const { - assert(!empty()); + FBSTRING_ASSERT(!empty()); // Should be begin()[size() - 1], but that branches twice return *(end() - 1); } value_type& front() { return *begin(); } value_type& back() { - assert(!empty()); + FBSTRING_ASSERT(!empty()); // Should be begin()[size() - 1], but that branches twice return *(end() - 1); } void pop_back() { - assert(!empty()); + FBSTRING_ASSERT(!empty()); store_.shrink(1); } @@ -1364,7 +1391,7 @@ public: basic_fbstring& append(const value_type* s, size_type n); basic_fbstring& append(const value_type* s) { - return append(s, traits_type::length(s)); + return append(s, traitsLength(s)); } basic_fbstring& append(size_type n, value_type c); @@ -1398,7 +1425,7 @@ public: basic_fbstring& assign(const value_type* s, const size_type n); basic_fbstring& assign(const value_type* s) { - return assign(s, traits_type::length(s)); + return assign(s, traitsLength(s)); } basic_fbstring& assign(std::initializer_list il) { @@ -1428,7 +1455,7 @@ public: } basic_fbstring& insert(size_type pos, const value_type* s) { - return insert(pos, s, traits_type::length(s)); + return insert(pos, s, traitsLength(s)); } basic_fbstring& insert(size_type pos, size_type n, value_type c) { @@ -1537,7 +1564,7 @@ public: // Replaces at most n1 chars of *this, starting with pos, with chars from s basic_fbstring& replace(size_type pos, size_type n1, const value_type* s) { - return replace(pos, n1, s, traits_type::length(s)); + return replace(pos, n1, s, traitsLength(s)); } // Replaces at most n1 chars of *this, starting with pos, with n2 @@ -1563,7 +1590,7 @@ public: } basic_fbstring& replace(iterator i1, iterator i2, const value_type* s) { - return replace(i1, i2, s, traits_type::length(s)); + return replace(i1, i2, s, traitsLength(s)); } private: @@ -1666,7 +1693,7 @@ private: const; size_type find(const value_type* s, size_type pos = 0) const { - return find(s, pos, traits_type::length(s)); + return find(s, pos, traitsLength(s)); } size_type find (value_type c, size_type pos = 0) const { @@ -1680,7 +1707,7 @@ private: size_type rfind(const value_type* s, size_type pos, size_type n) const; size_type rfind(const value_type* s, size_type pos = npos) const { - return rfind(s, pos, traits_type::length(s)); + return rfind(s, pos, traitsLength(s)); } size_type rfind(value_type c, size_type pos = npos) const { @@ -1695,7 +1722,7 @@ private: const; size_type find_first_of(const value_type* s, size_type pos = 0) const { - return find_first_of(s, pos, traits_type::length(s)); + return find_first_of(s, pos, traitsLength(s)); } size_type find_first_of(value_type c, size_type pos = 0) const { @@ -1711,7 +1738,7 @@ private: size_type find_last_of (const value_type* s, size_type pos = npos) const { - return find_last_of(s, pos, traits_type::length(s)); + return find_last_of(s, pos, traitsLength(s)); } size_type find_last_of (value_type c, size_type pos = npos) const { @@ -1728,7 +1755,7 @@ private: size_type find_first_not_of(const value_type* s, size_type pos = 0) const { - return find_first_not_of(s, pos, traits_type::length(s)); + return find_first_not_of(s, pos, traitsLength(s)); } size_type find_first_not_of(value_type c, size_type pos = 0) const { @@ -1745,7 +1772,7 @@ private: size_type find_last_not_of(const value_type* s, size_type pos = npos) const { - return find_last_not_of(s, pos, traits_type::length(s)); + return find_last_not_of(s, pos, traitsLength(s)); } size_type find_last_not_of (value_type c, size_type pos = npos) const { @@ -1778,7 +1805,7 @@ private: int compare(size_type pos1, size_type n1, const value_type* s) const { - return compare(pos1, n1, s, traits_type::length(s)); + return compare(pos1, n1, s, traitsLength(s)); } int compare(size_type pos1, size_type n1, @@ -1800,9 +1827,9 @@ private: // Code from Jean-Francois Bastien (03/26/2007) int compare(const value_type* s) const { - // Could forward to compare(0, size(), s, traits_type::length(s)) + // Could forward to compare(0, size(), s, traitsLength(s)) // but that does two extra checks - const size_type n1(size()), n2(traits_type::length(s)); + const size_type n1(size()), n2(traitsLength(s)); const int r = traits_type::compare(data(), s, std::min(n1, n2)); return r != 0 ? r : n1 > n2 ? 1 : n1 < n2 ? -1 : 0; } @@ -1813,7 +1840,7 @@ private: }; template -inline typename basic_fbstring::size_type +FOLLY_MALLOC_NOINLINE inline typename basic_fbstring::size_type basic_fbstring::traitsLength(const value_type* s) { return s ? traits_type::length(s) : (std::__throw_logic_error( @@ -1850,8 +1877,13 @@ inline basic_fbstring& basic_fbstring::operator=( } template -inline basic_fbstring& basic_fbstring::operator=( - const value_type c) { +template +inline typename std::enable_if< + std::is_same< + typename std::decay::type, + typename basic_fbstring::value_type>::value, + basic_fbstring&>::type +basic_fbstring::operator=(TP c) { Invariant checker(*this); if (empty()) { @@ -1879,7 +1911,7 @@ inline void basic_fbstring::resize( auto pData = store_.expandNoinit(delta); fbstring_detail::podFill(pData, pData + delta, c); } - assert(this->size() == n); + FBSTRING_ASSERT(this->size() == n); } template @@ -1889,7 +1921,7 @@ inline basic_fbstring& basic_fbstring::append( auto desiredSize = size() + str.size(); #endif append(str.data(), str.size()); - assert(size() == desiredSize); + FBSTRING_ASSERT(size() == desiredSize); return *this; } @@ -1903,8 +1935,8 @@ inline basic_fbstring& basic_fbstring::append( } template -inline basic_fbstring& basic_fbstring::append( - const value_type* s, size_type n) { +FOLLY_MALLOC_NOINLINE inline basic_fbstring& +basic_fbstring::append(const value_type* s, size_type n) { Invariant checker(*this); if (FBSTRING_UNLIKELY(!n)) { @@ -1923,7 +1955,7 @@ inline basic_fbstring& basic_fbstring::append( // info. std::less_equal le; if (FBSTRING_UNLIKELY(le(oldData, s) && !le(oldData + oldSize, s))) { - assert(le(s + n, oldData + oldSize)); + FBSTRING_ASSERT(le(s + n, oldData + oldSize)); // expandNoinit() could have moved the storage, restore the source. s = data() + (s - oldData); fbstring_detail::podMove(s, s + n, pData); @@ -1931,7 +1963,7 @@ inline basic_fbstring& basic_fbstring::append( fbstring_detail::podCopy(s, s + n, pData); } - assert(size() == oldSize + n); + FBSTRING_ASSERT(size() == oldSize + n); return *this; } @@ -1954,18 +1986,17 @@ inline basic_fbstring& basic_fbstring::assign( } template -inline basic_fbstring& basic_fbstring::assign( - const value_type* s, const size_type n) { +FOLLY_MALLOC_NOINLINE inline basic_fbstring& +basic_fbstring::assign(const value_type* s, const size_type n) { Invariant checker(*this); - // s can alias this, we need to use podMove. if (n == 0) { resize(0); } else if (size() >= n) { // s can alias this, we need to use podMove. fbstring_detail::podMove(s, s + n, store_.mutableData()); store_.shrink(size() - n); - assert(size() == n); + FBSTRING_ASSERT(size() == n); } else { // If n is larger than size(), s cannot alias this string's // storage. @@ -1975,7 +2006,7 @@ inline basic_fbstring& basic_fbstring::assign( fbstring_detail::podCopy(s, s + n, store_.expandNoinit(n)); } - assert(size() == n); + FBSTRING_ASSERT(size() == n); return *this; } @@ -2003,8 +2034,8 @@ basic_fbstring::getlineImpl(istream_type & is, value_type delim) { break; } - assert(size == this->size()); - assert(size == capacity()); + FBSTRING_ASSERT(size == this->size()); + FBSTRING_ASSERT(size == capacity()); // Start at minimum allocation 63 + terminator = 64. reserve(std::max(63, 3 * size / 2)); // Clear the error so we can continue reading. @@ -2054,7 +2085,7 @@ basic_fbstring::find( // Here we know that the last char matches // Continue in pedestrian mode for (size_t j = 0;;) { - assert(j < nsize); + FBSTRING_ASSERT(j < nsize); if (i[j] != needle[j]) { // Not found, we can skip // Compute the skip value lazily @@ -2083,7 +2114,7 @@ basic_fbstring::insertImplDiscr( const_iterator i, size_type n, value_type c, std::true_type) { Invariant checker(*this); - assert(i >= cbegin() && i <= cend()); + FBSTRING_ASSERT(i >= cbegin() && i <= cend()); const size_type pos = i - cbegin(); auto oldSize = size(); @@ -2114,10 +2145,10 @@ basic_fbstring::insertImpl( std::forward_iterator_tag) { Invariant checker(*this); - assert(i >= cbegin() && i <= cend()); + FBSTRING_ASSERT(i >= cbegin() && i <= cend()); const size_type pos = i - cbegin(); auto n = std::distance(s1, s2); - assert(n >= 0); + FBSTRING_ASSERT(n >= 0); auto oldSize = size(); store_.expandNoinit(n, /* expGrowth = */ true); @@ -2153,9 +2184,9 @@ inline basic_fbstring& basic_fbstring::replaceImplDiscr( const value_type* s, size_type n, std::integral_constant) { - assert(i1 <= i2); - assert(begin() <= i1 && i1 <= end()); - assert(begin() <= i2 && i2 <= end()); + FBSTRING_ASSERT(i1 <= i2); + FBSTRING_ASSERT(begin() <= i1 && i1 <= end()); + FBSTRING_ASSERT(begin() <= i2 && i2 <= end()); return replace(i1, i2, s, s + n); } @@ -2174,7 +2205,7 @@ inline basic_fbstring& basic_fbstring::replaceImplDiscr( std::fill(i1, i2, c); insert(i2, n2 - n1, c); } - assert(isSane()); + FBSTRING_ASSERT(isSane()); return *this; } @@ -2228,9 +2259,9 @@ inline void basic_fbstring::replaceImpl( } auto const n1 = i2 - i1; - assert(n1 >= 0); + FBSTRING_ASSERT(n1 >= 0); auto const n2 = std::distance(s1, s2); - assert(n2 >= 0); + FBSTRING_ASSERT(n2 >= 0); if (n1 > n2) { // shrinks @@ -2241,7 +2272,7 @@ inline void basic_fbstring::replaceImpl( s1 = fbstring_detail::copy_n(s1, n1, i1).first; insert(i2, s1, s2); } - assert(isSane()); + FBSTRING_ASSERT(isSane()); } template @@ -2634,13 +2665,13 @@ std::basic_istream< std::basic_istream::value_type, typename basic_fbstring::traits_type>& is, basic_fbstring& str) { - typename std::basic_istream::sentry sentry(is); - typedef std::basic_istream::value_type, - typename basic_fbstring::traits_type> - __istream_type; - typedef typename __istream_type::ios_base __ios_base; + typedef std::basic_istream< + typename basic_fbstring::value_type, + typename basic_fbstring::traits_type> + _istream_type; + typename _istream_type::sentry sentry(is); size_t extracted = 0; - auto err = __ios_base::goodbit; + auto err = _istream_type::goodbit; if (sentry) { auto n = is.width(); if (n <= 0) { @@ -2649,7 +2680,7 @@ std::basic_istream< str.erase(); for (auto got = is.rdbuf()->sgetc(); extracted != size_t(n); ++extracted) { if (got == T::eof()) { - err |= __ios_base::eofbit; + err |= _istream_type::eofbit; is.width(0); break; } @@ -2661,7 +2692,7 @@ std::basic_istream< } } if (!extracted) { - err |= __ios_base::failbit; + err |= _istream_type::failbit; } if (err) { is.setstate(err); @@ -2678,28 +2709,31 @@ operator<<( typename basic_fbstring::traits_type>& os, const basic_fbstring& str) { #if _LIBCPP_VERSION - typename std::basic_ostream< - typename basic_fbstring::value_type, - typename basic_fbstring::traits_type>::sentry __s(os); - if (__s) { + typedef std::basic_ostream< + typename basic_fbstring::value_type, + typename basic_fbstring::traits_type> + _ostream_type; + typename _ostream_type::sentry _s(os); + if (_s) { typedef std::ostreambuf_iterator< typename basic_fbstring::value_type, typename basic_fbstring::traits_type> _Ip; size_t __len = str.size(); bool __left = - (os.flags() & std::ios_base::adjustfield) == std::ios_base::left; + (os.flags() & _ostream_type::adjustfield) == _ostream_type::left; if (__pad_and_output(_Ip(os), str.data(), __left ? str.data() + __len : str.data(), str.data() + __len, os, os.fill()).failed()) { - os.setstate(std::ios_base::badbit | std::ios_base::failbit); + os.setstate(_ostream_type::badbit | _ostream_type::failbit); } } #elif defined(_MSC_VER) + typedef decltype(os.precision()) streamsize; // MSVC doesn't define __ostream_insert - os.write(str.data(), str.size()); + os.write(str.data(), static_cast(str.size())); #else std::__ostream_insert(os, str.data(), str.size()); #endif @@ -2713,34 +2747,90 @@ constexpr typename basic_fbstring::size_type #ifndef _LIBSTDCXX_FBSTRING // basic_string compatibility routines -template -inline -bool operator==(const basic_fbstring& lhs, - const std::string& rhs) { +template +inline bool operator==( + const basic_fbstring& lhs, + const std::basic_string& rhs) { return lhs.compare(0, lhs.size(), rhs.data(), rhs.size()) == 0; } -template -inline -bool operator==(const std::string& lhs, - const basic_fbstring& rhs) { +template +inline bool operator==( + const std::basic_string& lhs, + const basic_fbstring& rhs) { return rhs == lhs; } -template -inline -bool operator!=(const basic_fbstring& lhs, - const std::string& rhs) { +template +inline bool operator!=( + const basic_fbstring& lhs, + const std::basic_string& rhs) { return !(lhs == rhs); } -template -inline -bool operator!=(const std::string& lhs, - const basic_fbstring& rhs) { +template +inline bool operator!=( + const std::basic_string& lhs, + const basic_fbstring& rhs) { return !(lhs == rhs); } +template +inline bool operator<( + const basic_fbstring& lhs, + const std::basic_string& rhs) { + return lhs.compare(0, lhs.size(), rhs.data(), rhs.size()) < 0; +} + +template +inline bool operator>( + const basic_fbstring& lhs, + const std::basic_string& rhs) { + return lhs.compare(0, lhs.size(), rhs.data(), rhs.size()) > 0; +} + +template +inline bool operator<( + const std::basic_string& lhs, + const basic_fbstring& rhs) { + return rhs > lhs; +} + +template +inline bool operator>( + const std::basic_string& lhs, + const basic_fbstring& rhs) { + return rhs < lhs; +} + +template +inline bool operator<=( + const basic_fbstring& lhs, + const std::basic_string& rhs) { + return !(lhs > rhs); +} + +template +inline bool operator>=( + const basic_fbstring& lhs, + const std::basic_string& rhs) { + return !(lhs < rhs); +} + +template +inline bool operator<=( + const std::basic_string& lhs, + const basic_fbstring& rhs) { + return !(lhs > rhs); +} + +template +inline bool operator>=( + const std::basic_string& lhs, + const basic_fbstring& rhs) { + return !(lhs < rhs); +} + #if !defined(_LIBSTDCXX_FBSTRING) typedef basic_fbstring fbstring; #endif @@ -2805,8 +2895,4 @@ FOLLY_FBSTRING_HASH #undef throw #undef FBSTRING_LIKELY #undef FBSTRING_UNLIKELY - -#ifdef FOLLY_DEFINED_NDEBUG_FOR_FBSTRING -#undef NDEBUG -#undef FOLLY_DEFINED_NDEBUG_FOR_FBSTRING -#endif // FOLLY_DEFINED_NDEBUG_FOR_FBSTRING +#undef FBSTRING_ASSERT