/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef vm_InlineCharBuffer_inl_h #define vm_InlineCharBuffer_inl_h #include "vm/StringType-inl.h" namespace js { template struct MaximumInlineLength; template <> struct MaximumInlineLength { static constexpr size_t value = JSFatInlineString::MAX_LENGTH_LATIN1; }; template <> struct MaximumInlineLength { static constexpr size_t value = JSFatInlineString::MAX_LENGTH_TWO_BYTE; }; // Character buffer class used for ToLowerCase and ToUpperCase operations, as // well as other string operations where the final string length is known in // advance. // // Case conversion operations normally return a string with the same length as // the input string. To avoid over-allocation, we optimistically allocate an // array with same size as the input string and only when we detect special // casing characters, which can change the output string length, we reallocate // the output buffer to the final string length. // // As a further mean to improve runtime performance, the character buffer // contains an inline storage, so we don't need to heap-allocate an array when // a JSInlineString will be used for the output string. // // Why not use mozilla::Vector instead? mozilla::Vector doesn't provide enough // fine-grained control to avoid over-allocation when (re)allocating for exact // buffer sizes. This led to visible performance regressions in ยต-benchmarks. template class MOZ_NON_PARAM InlineCharBuffer { static constexpr size_t InlineCapacity = MaximumInlineLength::value; CharT inlineStorage[InlineCapacity]; UniquePtr heapStorage; #ifdef DEBUG // In debug mode, we keep track of the requested string lengths to ensure // all character buffer methods are called in the correct order and with // the expected argument values. size_t lastRequestedLength = 0; void assertValidRequest(size_t expectedLastLength, size_t length) { MOZ_ASSERT(length >= expectedLastLength, "cannot shrink requested length"); MOZ_ASSERT(lastRequestedLength == expectedLastLength); lastRequestedLength = length; } #else void assertValidRequest(size_t expectedLastLength, size_t length) {} #endif public: CharT* get() { return heapStorage ? heapStorage.get() : inlineStorage; } bool maybeAlloc(JSContext* cx, size_t length) { assertValidRequest(0, length); if (length <= InlineCapacity) { return true; } MOZ_ASSERT(!heapStorage, "heap storage already allocated"); heapStorage = cx->make_pod_arena_array(js::StringBufferArena, length); return !!heapStorage; } bool maybeRealloc(JSContext* cx, size_t oldLength, size_t newLength) { assertValidRequest(oldLength, newLength); if (newLength <= InlineCapacity) { return true; } if (!heapStorage) { heapStorage = cx->make_pod_arena_array(js::StringBufferArena, newLength); if (!heapStorage) { return false; } MOZ_ASSERT(oldLength <= InlineCapacity); mozilla::PodCopy(heapStorage.get(), inlineStorage, oldLength); return true; } CharT* oldChars = heapStorage.release(); CharT* newChars = cx->pod_arena_realloc(js::StringBufferArena, oldChars, oldLength, newLength); if (!newChars) { js_free(oldChars); return false; } heapStorage.reset(newChars); return true; } JSString* toStringDontDeflate(JSContext* cx, size_t length, js::gc::Heap heap = js::gc::Heap::Default) { MOZ_ASSERT(length == lastRequestedLength); if (JSInlineString::lengthFits(length)) { MOZ_ASSERT( !heapStorage, "expected only inline storage when length fits in inline string"); if (JSString* str = TryEmptyOrStaticString(cx, inlineStorage, length)) { return str; } mozilla::Range range(inlineStorage, length); return NewInlineString(cx, range, heap); } MOZ_ASSERT(heapStorage, "heap storage was not allocated for non-inline string"); return NewStringDontDeflate(cx, std::move(heapStorage), length, heap); } JSString* toString(JSContext* cx, size_t length, js::gc::Heap heap = js::gc::Heap::Default) { MOZ_ASSERT(length == lastRequestedLength); if (JSInlineString::lengthFits(length)) { MOZ_ASSERT( !heapStorage, "expected only inline storage when length fits in inline string"); return NewStringCopyN(cx, inlineStorage, length, heap); } MOZ_ASSERT(heapStorage, "heap storage was not allocated for non-inline string"); return NewString(cx, std::move(heapStorage), length, heap); } }; } /* namespace js */ #endif /* vm_InlineCharBuffer_inl_h */