/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sw=4 et tw=99 ft=cpp: * * 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 jsvector_h_ #define jsvector_h_ #include "mozilla/Attributes.h" #include "mozilla/TypeTraits.h" #include "TemplateLib.h" #include "Utility.h" /* Silence dire "bugs in previous versions of MSVC have been fixed" warnings */ #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4345) #endif namespace js { class TempAllocPolicy; template class Vector; /* * This template class provides a default implementation for vector operations * when the element type is not known to be a POD, as judged by IsPod. */ template struct VectorImpl { /* Destroys constructed objects in the range [begin, end). */ static inline void destroy(T *begin, T *end) { for (T *p = begin; p != end; ++p) p->~T(); } /* Constructs objects in the uninitialized range [begin, end). */ static inline void initialize(T *begin, T *end) { for (T *p = begin; p != end; ++p) new(p) T(); } /* * Copy-constructs objects in the uninitialized range * [dst, dst+(srcend-srcbeg)) from the range [srcbeg, srcend). */ template static inline void copyConstruct(T *dst, const U *srcbeg, const U *srcend) { for (const U *p = srcbeg; p != srcend; ++p, ++dst) new(dst) T(*p); } /* * Move-constructs objects in the uninitialized range * [dst, dst+(srcend-srcbeg)) from the range [srcbeg, srcend). */ template static inline void moveConstruct(T *dst, const U *srcbeg, const U *srcend) { for (const U *p = srcbeg; p != srcend; ++p, ++dst) new(dst) T(Move(*p)); } /* * Copy-constructs objects in the uninitialized range [dst, dst+n) from the * same object u. */ template static inline void copyConstructN(T *dst, size_t n, const U &u) { for (T *end = dst + n; dst != end; ++dst) new(dst) T(u); } /* * Grows the given buffer to have capacity newcap, preserving the objects * constructed in the range [begin, end) and updating v. Assumes that (1) * newcap has not overflowed, and (2) multiplying newcap by sizeof(T) will * not overflow. */ static inline bool growTo(Vector &v, size_t newcap) { JS_ASSERT(!v.usingInlineStorage()); T *newbuf = reinterpret_cast(v.malloc_(newcap * sizeof(T))); if (!newbuf) return false; for (T *dst = newbuf, *src = v.beginNoCheck(); src != v.endNoCheck(); ++dst, ++src) new(dst) T(Move(*src)); VectorImpl::destroy(v.beginNoCheck(), v.endNoCheck()); v.free_(v.mBegin); v.mBegin = newbuf; /* v.mLength is unchanged. */ v.mCapacity = newcap; return true; } }; /* * This partial template specialization provides a default implementation for * vector operations when the element type is known to be a POD, as judged by * IsPod. */ template struct VectorImpl { static inline void destroy(T *, T *) {} static inline void initialize(T *begin, T *end) { /* * You would think that memset would be a big win (or even break even) * when we know T is a POD. But currently it's not. This is probably * because |append| tends to be given small ranges and memset requires * a function call that doesn't get inlined. * * memset(begin, 0, sizeof(T) * (end-begin)); */ for (T *p = begin; p != end; ++p) new(p) T(); } template static inline void copyConstruct(T *dst, const U *srcbeg, const U *srcend) { /* * See above memset comment. Also, notice that copyConstruct is * currently templated (T != U), so memcpy won't work without * requiring T == U. * * memcpy(dst, srcbeg, sizeof(T) * (srcend - srcbeg)); */ for (const U *p = srcbeg; p != srcend; ++p, ++dst) *dst = *p; } template static inline void moveConstruct(T *dst, const U *srcbeg, const U *srcend) { copyConstruct(dst, srcbeg, srcend); } static inline void copyConstructN(T *dst, size_t n, const T &t) { for (T *p = dst, *end = dst + n; p != end; ++p) *p = t; } static inline bool growTo(Vector &v, size_t newcap) { JS_ASSERT(!v.usingInlineStorage()); size_t bytes = sizeof(T) * newcap; size_t oldBytes = sizeof(T) * v.mCapacity; T *newbuf = reinterpret_cast(v.realloc_(v.mBegin, oldBytes, bytes)); if (!newbuf) return false; v.mBegin = newbuf; /* v.mLength is unchanged. */ v.mCapacity = newcap; return true; } }; /* * JS-friendly, STL-like container providing a short-lived, dynamic buffer. * Vector calls the constructors/destructors of all elements stored in * its internal buffer, so non-PODs may be safely used. Additionally, * Vector will store the first N elements in-place before resorting to * dynamic allocation. * * T requirements: * - default and copy constructible, assignable, destructible * - operations do not throw * N requirements: * - any value, however, N is clamped to min/max values * AllocPolicy: * - see "Allocation policies" in jsalloc.h (default js::TempAllocPolicy) * * N.B: Vector is not reentrant: T member functions called during Vector member * functions must not call back into the same object. */ template class Vector : private AllocPolicy { // typedef typename tl::StaticAssert::result>::result _; /* utilities */ static const bool sElemIsPod = mozilla::IsPod::result; typedef VectorImpl Impl; friend struct VectorImpl; bool calculateNewCapacity(size_t curLength, size_t lengthInc, size_t &newCap); bool growStorageBy(size_t lengthInc); bool growHeapStorageBy(size_t lengthInc); bool convertToHeapStorage(size_t lengthInc); template inline bool growByImpl(size_t inc); /* magic constants */ static const int sMaxInlineBytes = 1024; /* compute constants */ /* * Consider element size to be 1 for buffer sizing if there are * 0 inline elements. This allows us to compile when the definition * of the element type is not visible here. * * Explicit specialization is only allowed at namespace scope, so * in order to keep everything here, we use a dummy template * parameter with partial specialization. */ template struct ElemSize { static const size_t result = sizeof(T); }; template struct ElemSize<0, Dummy> { static const size_t result = 1; }; static const size_t sInlineCapacity = tl::Min::result>::result; /* Calculate inline buffer size; avoid 0-sized array. */ static const size_t sInlineBytes = tl::Max<1, sInlineCapacity * ElemSize::result>::result; /* member data */ /* * Pointer to the buffer, be it inline or heap-allocated. Only [mBegin, * mBegin + mLength) hold valid constructed T objects. The range [mBegin + * mLength, mBegin + mCapacity) holds uninitialized memory. The range * [mBegin + mLength, mBegin + mReserved) also holds uninitialized memory * previously allocated by a call to reserve(). */ T *mBegin; size_t mLength; /* Number of elements in the Vector. */ size_t mCapacity; /* Max number of elements storable in the Vector without resizing. */ #ifdef DEBUG size_t mReserved; /* Max elements of reserved or used space in this vector. */ #endif mozilla::AlignedStorage storage; #ifdef DEBUG friend class ReentrancyGuard; bool entered; #endif Vector(const Vector &) MOZ_DELETE; Vector &operator=(const Vector &) MOZ_DELETE; /* private accessors */ bool usingInlineStorage() const { return mBegin == inlineStorage(); } T *inlineStorage() const { return (T *)storage.addr(); } T *beginNoCheck() const { return mBegin; } T *endNoCheck() { return mBegin + mLength; } const T *endNoCheck() const { return mBegin + mLength; } #ifdef DEBUG size_t reserved() const { JS_ASSERT(mReserved <= mCapacity); JS_ASSERT(mLength <= mReserved); return mReserved; } #endif /* Append operations guaranteed to succeed due to pre-reserved space. */ template void internalAppend(U t); void internalAppendN(const T &t, size_t n); template void internalAppend(const U *begin, size_t length); template void internalAppend(const Vector &other); public: static const size_t sMaxInlineStorage = N; typedef T ElementType; Vector(AllocPolicy = AllocPolicy()); Vector(MoveRef); /* Move constructor. */ Vector &operator=(MoveRef); /* Move assignment. */ ~Vector(); /* accessors */ const AllocPolicy &allocPolicy() const { return *this; } AllocPolicy &allocPolicy() { return *this; } enum { InlineLength = N }; size_t length() const { return mLength; } bool empty() const { return mLength == 0; } size_t capacity() const { return mCapacity; } T *begin() { JS_ASSERT(!entered); return mBegin; } const T *begin() const { JS_ASSERT(!entered); return mBegin; } T *end() { JS_ASSERT(!entered); return mBegin + mLength; } const T *end() const { JS_ASSERT(!entered); return mBegin + mLength; } T &operator[](size_t i) { JS_ASSERT(!entered && i < mLength); return begin()[i]; } const T &operator[](size_t i) const { JS_ASSERT(!entered && i < mLength); return begin()[i]; } T &back() { JS_ASSERT(!entered && !empty()); return *(end() - 1); } const T &back() const { JS_ASSERT(!entered && !empty()); return *(end() - 1); } class Range { friend class Vector; T *cur, *end; Range(T *cur, T *end) : cur(cur), end(end) {} public: Range() {} bool empty() const { return cur == end; } size_t remain() const { return end - cur; } T &front() const { return *cur; } void popFront() { JS_ASSERT(!empty()); ++cur; } T popCopyFront() { JS_ASSERT(!empty()); return *cur++; } }; Range all() { return Range(begin(), end()); } /* mutators */ /* If reserve(length() + N) succeeds, the N next appends are guaranteed to succeed. */ bool reserve(size_t capacity); /* * Destroy elements in the range [end() - incr, end()). Does not deallocate * or unreserve storage for those elements. */ void shrinkBy(size_t incr); /* Grow the vector by incr elements. */ bool growBy(size_t incr); /* Call shrinkBy or growBy based on whether newSize > length(). */ bool resize(size_t newLength); /* Leave new elements as uninitialized memory. */ bool growByUninitialized(size_t incr); bool resizeUninitialized(size_t newLength); /* Shorthand for shrinkBy(length()). */ void clear(); /* Clears and releases any heap-allocated storage. */ void clearAndFree(); /* If true, appending |needed| elements will not call realloc(). */ bool canAppendWithoutRealloc(size_t needed) const; /* * Potentially fallible append operations. * * The function templates that take an unspecified type U require a * const T & or a MoveRef. The MoveRef variants move their * operands into the vector, instead of copying them. If they fail, the * operand is left unmoved. */ template bool append(U t); bool appendN(const T &t, size_t n); template bool append(const U *begin, const U *end); template bool append(const U *begin, size_t length); template bool append(const Vector &other); /* * Guaranteed-infallible append operations for use upon vectors whose * memory has been pre-reserved. */ void infallibleAppend(const T &t) { internalAppend(t); } void infallibleAppendN(const T &t, size_t n) { internalAppendN(t, n); } template void infallibleAppend(const U *begin, const U *end) { internalAppend(begin, mozilla::PointerRangeSize(begin, end)); } template void infallibleAppend(const U *begin, size_t length) { internalAppend(begin, length); } template void infallibleAppend(const Vector &other) { internalAppend(other); } void popBack(); T popCopy(); /* * Transfers ownership of the internal buffer used by Vector to the caller. * After this call, the Vector is empty. Since the returned buffer may need * to be allocated (if the elements are currently stored in-place), the * call can fail, returning NULL. * * N.B. Although a T*, only the range [0, length()) is constructed. */ T *extractRawBuffer(); /* * Transfer ownership of an array of objects into the Vector. * N.B. This call assumes that there are no uninitialized elements in the * passed array. */ void replaceRawBuffer(T *p, size_t length); /* * Places |val| at position |p|, shifting existing elements * from |p| onward one position higher. */ bool insert(T *p, const T &val); /* * Removes the element |t|, which must fall in the bounds [begin, end), * shifting existing elements from |t + 1| onward one position lower. */ void erase(T *t); /* * Measure the size of the Vector's heap-allocated storage. */ size_t sizeOfExcludingThis(JSMallocSizeOfFun mallocSizeOf) const; /* * Like sizeOfExcludingThis, but also measures the size of the Vector * object (which must be heap-allocated) itself. */ size_t sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf) const; void swap(Vector &other); }; /* This does the re-entrancy check plus several other sanity checks. */ #define REENTRANCY_GUARD_ET_AL \ ReentrancyGuard g(*this); \ JS_ASSERT_IF(usingInlineStorage(), mCapacity == sInlineCapacity); \ JS_ASSERT(reserved() <= mCapacity); \ JS_ASSERT(mLength <= reserved()); \ JS_ASSERT(mLength <= mCapacity) /* Vector Implementation */ template JS_ALWAYS_INLINE Vector::Vector(AllocPolicy ap) : AllocPolicy(ap), mBegin((T *)storage.addr()), mLength(0), mCapacity(sInlineCapacity) #ifdef DEBUG , mReserved(0), entered(false) #endif {} /* Move constructor. */ template JS_ALWAYS_INLINE Vector::Vector(MoveRef rhs) : AllocPolicy(rhs) #ifdef DEBUG , entered(false) #endif { mLength = rhs->mLength; mCapacity = rhs->mCapacity; #ifdef DEBUG mReserved = rhs->mReserved; #endif if (rhs->usingInlineStorage()) { /* We can't move the buffer over in this case, so copy elements. */ mBegin = (T *)storage.addr(); Impl::moveConstruct(mBegin, rhs->beginNoCheck(), rhs->endNoCheck()); /* * Leave rhs's mLength, mBegin, mCapacity, and mReserved as they are. * The elements in its in-line storage still need to be destroyed. */ } else { /* * Take src's buffer, and turn src into an empty vector using * in-line storage. */ mBegin = rhs->mBegin; rhs->mBegin = (T *) rhs->storage.addr(); rhs->mCapacity = sInlineCapacity; rhs->mLength = 0; #ifdef DEBUG rhs->mReserved = 0; #endif } } /* Move assignment. */ template JS_ALWAYS_INLINE Vector & Vector::operator=(MoveRef rhs) { this->~Vector(); new(this) Vector(rhs); return *this; } template JS_ALWAYS_INLINE Vector::~Vector() { REENTRANCY_GUARD_ET_AL; Impl::destroy(beginNoCheck(), endNoCheck()); if (!usingInlineStorage()) this->free_(beginNoCheck()); } /* * Calculate a new capacity that is at least lengthInc greater than * curLength and check for overflow. */ template STATIC_POSTCONDITION(!return || newCap >= curLength + lengthInc) #ifdef DEBUG /* gcc (ARM, x86) compiler bug workaround - See bug 694694 */ JS_NEVER_INLINE bool #else inline bool #endif Vector::calculateNewCapacity(size_t curLength, size_t lengthInc, size_t &newCap) { size_t newMinCap = curLength + lengthInc; /* * Check for overflow in the above addition, below CEILING_LOG2, and later * multiplication by sizeof(T). */ if (newMinCap < curLength || newMinCap & tl::MulOverflowMask<2 * sizeof(T)>::result) { this->reportAllocOverflow(); return false; } /* Round up to next power of 2. */ newCap = RoundUpPow2(newMinCap); /* * Do not allow a buffer large enough that the expression ((char *)end() - * (char *)begin()) overflows ptrdiff_t. See Bug 510319. */ if (newCap & tl::UnsafeRangeSizeMask::result) { this->reportAllocOverflow(); return false; } return true; } /* * This function will grow the current heap capacity to have capacity * (mLength + lengthInc) and fail on OOM or integer overflow. */ template JS_ALWAYS_INLINE bool Vector::growHeapStorageBy(size_t lengthInc) { JS_ASSERT(!usingInlineStorage()); size_t newCap; return calculateNewCapacity(mLength, lengthInc, newCap) && Impl::growTo(*this, newCap); } /* * This function will create a new heap buffer with capacity (mLength + * lengthInc()), move all elements in the inline buffer to this new buffer, * and fail on OOM or integer overflow. */ template inline bool Vector::convertToHeapStorage(size_t lengthInc) { JS_ASSERT(usingInlineStorage()); size_t newCap; if (!calculateNewCapacity(mLength, lengthInc, newCap)) return false; /* Allocate buffer. */ T *newBuf = reinterpret_cast(this->malloc_(newCap * sizeof(T))); if (!newBuf) return false; /* Copy inline elements into heap buffer. */ Impl::moveConstruct(newBuf, beginNoCheck(), endNoCheck()); Impl::destroy(beginNoCheck(), endNoCheck()); /* Switch in heap buffer. */ mBegin = newBuf; /* mLength is unchanged. */ mCapacity = newCap; return true; } template JS_NEVER_INLINE bool Vector::growStorageBy(size_t incr) { JS_ASSERT(mLength + incr > mCapacity); return usingInlineStorage() ? convertToHeapStorage(incr) : growHeapStorageBy(incr); } template inline bool Vector::reserve(size_t request) { REENTRANCY_GUARD_ET_AL; if (request > mCapacity && !growStorageBy(request - mLength)) return false; #ifdef DEBUG if (request > mReserved) mReserved = request; JS_ASSERT(mLength <= mReserved); JS_ASSERT(mReserved <= mCapacity); #endif return true; } template inline void Vector::shrinkBy(size_t incr) { REENTRANCY_GUARD_ET_AL; JS_ASSERT(incr <= mLength); Impl::destroy(endNoCheck() - incr, endNoCheck()); mLength -= incr; } template template JS_ALWAYS_INLINE bool Vector::growByImpl(size_t incr) { REENTRANCY_GUARD_ET_AL; if (incr > mCapacity - mLength && !growStorageBy(incr)) return false; JS_ASSERT(mLength + incr <= mCapacity); T *newend = endNoCheck() + incr; if (InitNewElems) Impl::initialize(endNoCheck(), newend); mLength += incr; #ifdef DEBUG if (mLength > mReserved) mReserved = mLength; #endif return true; } template JS_ALWAYS_INLINE bool Vector::growBy(size_t incr) { return growByImpl(incr); } template JS_ALWAYS_INLINE bool Vector::growByUninitialized(size_t incr) { return growByImpl(incr); } template STATIC_POSTCONDITION(!return || ubound(this->begin()) >= newLength) inline bool Vector::resize(size_t newLength) { size_t curLength = mLength; if (newLength > curLength) return growBy(newLength - curLength); shrinkBy(curLength - newLength); return true; } template JS_ALWAYS_INLINE bool Vector::resizeUninitialized(size_t newLength) { size_t curLength = mLength; if (newLength > curLength) return growByUninitialized(newLength - curLength); shrinkBy(curLength - newLength); return true; } template inline void Vector::clear() { REENTRANCY_GUARD_ET_AL; Impl::destroy(beginNoCheck(), endNoCheck()); mLength = 0; } template inline void Vector::clearAndFree() { clear(); if (usingInlineStorage()) return; this->free_(beginNoCheck()); mBegin = (T *)storage.addr(); mCapacity = sInlineCapacity; #ifdef DEBUG mReserved = 0; #endif } template inline bool Vector::canAppendWithoutRealloc(size_t needed) const { return mLength + needed <= mCapacity; } template template JS_ALWAYS_INLINE bool Vector::append(U t) { REENTRANCY_GUARD_ET_AL; if (mLength == mCapacity && !growStorageBy(1)) return false; #ifdef DEBUG if (mLength + 1 > mReserved) mReserved = mLength + 1; #endif internalAppend(t); return true; } template template JS_ALWAYS_INLINE void Vector::internalAppend(U t) { JS_ASSERT(mLength + 1 <= mReserved); JS_ASSERT(mReserved <= mCapacity); new(endNoCheck()) T(t); ++mLength; } template JS_ALWAYS_INLINE bool Vector::appendN(const T &t, size_t needed) { REENTRANCY_GUARD_ET_AL; if (mLength + needed > mCapacity && !growStorageBy(needed)) return false; #ifdef DEBUG if (mLength + needed > mReserved) mReserved = mLength + needed; #endif internalAppendN(t, needed); return true; } template JS_ALWAYS_INLINE void Vector::internalAppendN(const T &t, size_t needed) { JS_ASSERT(mLength + needed <= mReserved); JS_ASSERT(mReserved <= mCapacity); Impl::copyConstructN(endNoCheck(), needed, t); mLength += needed; } template inline bool Vector::insert(T *p, const T &val) { JS_ASSERT(begin() <= p && p <= end()); size_t pos = p - begin(); JS_ASSERT(pos <= mLength); size_t oldLength = mLength; if (pos == oldLength) return append(val); { T oldBack = back(); if (!append(oldBack)) /* Dup the last element. */ return false; } for (size_t i = oldLength; i > pos; --i) (*this)[i] = (*this)[i - 1]; (*this)[pos] = val; return true; } template inline void Vector::erase(T *it) { JS_ASSERT(begin() <= it && it < end()); while (it + 1 != end()) { *it = *(it + 1); ++it; } popBack(); } template template JS_ALWAYS_INLINE bool Vector::append(const U *insBegin, const U *insEnd) { REENTRANCY_GUARD_ET_AL; size_t needed = mozilla::PointerRangeSize(insBegin, insEnd); if (mLength + needed > mCapacity && !growStorageBy(needed)) return false; #ifdef DEBUG if (mLength + needed > mReserved) mReserved = mLength + needed; #endif internalAppend(insBegin, needed); return true; } template template JS_ALWAYS_INLINE void Vector::internalAppend(const U *insBegin, size_t length) { JS_ASSERT(mLength + length <= mReserved); JS_ASSERT(mReserved <= mCapacity); Impl::copyConstruct(endNoCheck(), insBegin, insBegin + length); mLength += length; } template template inline bool Vector::append(const Vector &other) { return append(other.begin(), other.end()); } template template inline void Vector::internalAppend(const Vector &other) { internalAppend(other.begin(), other.length()); } template template JS_ALWAYS_INLINE bool Vector::append(const U *insBegin, size_t length) { return this->append(insBegin, insBegin + length); } template JS_ALWAYS_INLINE void Vector::popBack() { REENTRANCY_GUARD_ET_AL; JS_ASSERT(!empty()); --mLength; endNoCheck()->~T(); } template JS_ALWAYS_INLINE T Vector::popCopy() { T ret = back(); popBack(); return ret; } template inline T * Vector::extractRawBuffer() { T *ret; if (usingInlineStorage()) { ret = reinterpret_cast(this->malloc_(mLength * sizeof(T))); if (!ret) return NULL; Impl::copyConstruct(ret, beginNoCheck(), endNoCheck()); Impl::destroy(beginNoCheck(), endNoCheck()); /* mBegin, mCapacity are unchanged. */ mLength = 0; } else { ret = mBegin; mBegin = (T *)storage.addr(); mLength = 0; mCapacity = sInlineCapacity; #ifdef DEBUG mReserved = 0; #endif } return ret; } template inline void Vector::replaceRawBuffer(T *p, size_t length) { REENTRANCY_GUARD_ET_AL; /* Destroy what we have. */ Impl::destroy(beginNoCheck(), endNoCheck()); if (!usingInlineStorage()) this->free_(beginNoCheck()); /* Take in the new buffer. */ if (length <= sInlineCapacity) { /* * We convert to inline storage if possible, even though p might * otherwise be acceptable. Maybe this behaviour should be * specifiable with an argument to this function. */ mBegin = (T *)storage.addr(); mLength = length; mCapacity = sInlineCapacity; Impl::moveConstruct(mBegin, p, p + length); Impl::destroy(p, p + length); this->free_(p); } else { mBegin = p; mLength = length; mCapacity = length; } #ifdef DEBUG mReserved = length; #endif } template inline size_t Vector::sizeOfExcludingThis(JSMallocSizeOfFun mallocSizeOf) const { return usingInlineStorage() ? 0 : mallocSizeOf(beginNoCheck()); } template inline size_t Vector::sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf) const { return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); } template inline void Vector::swap(Vector &other) { // TODO Implement N != 0 JS_STATIC_ASSERT(N == 0); // This only works when inline storage is always empty. if (!usingInlineStorage() && other.usingInlineStorage()) { other.mBegin = mBegin; mBegin = inlineStorage(); } else if (usingInlineStorage() && !other.usingInlineStorage()) { mBegin = other.mBegin; other.mBegin = other.inlineStorage(); } else if (!usingInlineStorage() && !other.usingInlineStorage()) { Swap(mBegin, other.mBegin); } else { // This case is a no-op, since we'd set both to use their inline storage. } Swap(mLength, other.mLength); Swap(mCapacity, other.mCapacity); #ifdef DEBUG Swap(mReserved, other.mReserved); #endif } } /* namespace js */ #ifdef _MSC_VER #pragma warning(pop) #endif #endif /* jsvector_h_ */