summaryrefslogtreecommitdiffstats
path: root/js/src/ds/LifoAlloc.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/ds/LifoAlloc.h')
-rw-r--r--js/src/ds/LifoAlloc.h635
1 files changed, 635 insertions, 0 deletions
diff --git a/js/src/ds/LifoAlloc.h b/js/src/ds/LifoAlloc.h
new file mode 100644
index 000000000..f349cd476
--- /dev/null
+++ b/js/src/ds/LifoAlloc.h
@@ -0,0 +1,635 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 ds_LifoAlloc_h
+#define ds_LifoAlloc_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MemoryChecking.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/TemplateLib.h"
+#include "mozilla/TypeTraits.h"
+
+// This data structure supports stacky LIFO allocation (mark/release and
+// LifoAllocScope). It does not maintain one contiguous segment; instead, it
+// maintains a bunch of linked memory segments. In order to prevent malloc/free
+// thrashing, unused segments are deallocated when garbage collection occurs.
+
+#include "jsutil.h"
+
+namespace js {
+
+namespace detail {
+
+static const size_t LIFO_ALLOC_ALIGN = 8;
+
+MOZ_ALWAYS_INLINE
+char*
+AlignPtr(void* orig)
+{
+ static_assert(mozilla::tl::FloorLog2<LIFO_ALLOC_ALIGN>::value ==
+ mozilla::tl::CeilingLog2<LIFO_ALLOC_ALIGN>::value,
+ "LIFO_ALLOC_ALIGN must be a power of two");
+
+ char* result = (char*) ((uintptr_t(orig) + (LIFO_ALLOC_ALIGN - 1)) & (~LIFO_ALLOC_ALIGN + 1));
+ MOZ_ASSERT(uintptr_t(result) % LIFO_ALLOC_ALIGN == 0);
+ return result;
+}
+
+// Header for a chunk of memory wrangled by the LifoAlloc.
+class BumpChunk
+{
+ char* bump; // start of the available data
+ char* limit; // end of the data
+ BumpChunk* next_; // the next BumpChunk
+ size_t bumpSpaceSize; // size of the data area
+
+ char* headerBase() { return reinterpret_cast<char*>(this); }
+ char* bumpBase() const { return limit - bumpSpaceSize; }
+
+ explicit BumpChunk(size_t bumpSpaceSize)
+ : bump(reinterpret_cast<char*>(this) + sizeof(BumpChunk)),
+ limit(bump + bumpSpaceSize),
+ next_(nullptr), bumpSpaceSize(bumpSpaceSize)
+ {
+ MOZ_ASSERT(bump == AlignPtr(bump));
+ }
+
+ void setBump(void* ptr) {
+ MOZ_ASSERT(bumpBase() <= ptr);
+ MOZ_ASSERT(ptr <= limit);
+#if defined(DEBUG) || defined(MOZ_HAVE_MEM_CHECKS)
+ char* prevBump = bump;
+#endif
+ bump = static_cast<char*>(ptr);
+#ifdef DEBUG
+ MOZ_ASSERT(contains(prevBump));
+
+ // Clobber the now-free space.
+ if (prevBump > bump)
+ memset(bump, 0xcd, prevBump - bump);
+#endif
+
+ // Poison/Unpoison memory that we just free'd/allocated.
+#if defined(MOZ_HAVE_MEM_CHECKS)
+ if (prevBump > bump)
+ MOZ_MAKE_MEM_NOACCESS(bump, prevBump - bump);
+ else if (bump > prevBump)
+ MOZ_MAKE_MEM_UNDEFINED(prevBump, bump - prevBump);
+#endif
+ }
+
+ public:
+ BumpChunk* next() const { return next_; }
+ void setNext(BumpChunk* succ) { next_ = succ; }
+
+ size_t used() const { return bump - bumpBase(); }
+
+ void* start() const { return bumpBase(); }
+ void* end() const { return limit; }
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
+ return mallocSizeOf(this);
+ }
+
+ size_t computedSizeOfIncludingThis() {
+ return limit - headerBase();
+ }
+
+ void resetBump() {
+ setBump(headerBase() + sizeof(BumpChunk));
+ }
+
+ void* mark() const { return bump; }
+
+ void release(void* mark) {
+ MOZ_ASSERT(contains(mark));
+ MOZ_ASSERT(mark <= bump);
+ setBump(mark);
+ }
+
+ bool contains(void* mark) const {
+ return bumpBase() <= mark && mark <= limit;
+ }
+
+ bool canAlloc(size_t n);
+
+ size_t unused() {
+ return limit - AlignPtr(bump);
+ }
+
+ // Try to perform an allocation of size |n|, return null if not possible.
+ MOZ_ALWAYS_INLINE
+ void* tryAlloc(size_t n) {
+ char* aligned = AlignPtr(bump);
+ char* newBump = aligned + n;
+
+ if (newBump > limit)
+ return nullptr;
+
+ // Check for overflow.
+ if (MOZ_UNLIKELY(newBump < bump))
+ return nullptr;
+
+ MOZ_ASSERT(canAlloc(n)); // Ensure consistency between "can" and "try".
+ setBump(newBump);
+ return aligned;
+ }
+
+ static BumpChunk* new_(size_t chunkSize);
+ static void delete_(BumpChunk* chunk);
+};
+
+} // namespace detail
+
+// LIFO bump allocator: used for phase-oriented and fast LIFO allocations.
+//
+// Note: |latest| is not necessary "last". We leave BumpChunks latent in the
+// chain after they've been released to avoid thrashing before a GC.
+class LifoAlloc
+{
+ typedef detail::BumpChunk BumpChunk;
+
+ BumpChunk* first;
+ BumpChunk* latest;
+ BumpChunk* last;
+ size_t markCount;
+ size_t defaultChunkSize_;
+ size_t curSize_;
+ size_t peakSize_;
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+ bool fallibleScope_;
+#endif
+
+ void operator=(const LifoAlloc&) = delete;
+ LifoAlloc(const LifoAlloc&) = delete;
+
+ // Return a BumpChunk that can perform an allocation of at least size |n|
+ // and add it to the chain appropriately.
+ //
+ // Side effect: if retval is non-null, |first| and |latest| are initialized
+ // appropriately.
+ BumpChunk* getOrCreateChunk(size_t n);
+
+ void reset(size_t defaultChunkSize) {
+ MOZ_ASSERT(mozilla::RoundUpPow2(defaultChunkSize) == defaultChunkSize);
+ first = latest = last = nullptr;
+ defaultChunkSize_ = defaultChunkSize;
+ markCount = 0;
+ curSize_ = 0;
+ }
+
+ // Append unused chunks to the end of this LifoAlloc.
+ void appendUnused(BumpChunk* start, BumpChunk* end) {
+ MOZ_ASSERT(start && end);
+ if (last)
+ last->setNext(start);
+ else
+ first = latest = start;
+ last = end;
+ }
+
+ // Append used chunks to the end of this LifoAlloc. We act as if all the
+ // chunks in |this| are used, even if they're not, so memory may be wasted.
+ void appendUsed(BumpChunk* otherFirst, BumpChunk* otherLatest, BumpChunk* otherLast) {
+ MOZ_ASSERT(otherFirst && otherLatest && otherLast);
+ if (last)
+ last->setNext(otherFirst);
+ else
+ first = otherFirst;
+ latest = otherLatest;
+ last = otherLast;
+ }
+
+ void incrementCurSize(size_t size) {
+ curSize_ += size;
+ if (curSize_ > peakSize_)
+ peakSize_ = curSize_;
+ }
+ void decrementCurSize(size_t size) {
+ MOZ_ASSERT(curSize_ >= size);
+ curSize_ -= size;
+ }
+
+ MOZ_ALWAYS_INLINE
+ void* allocImpl(size_t n) {
+ void* result;
+ if (latest && (result = latest->tryAlloc(n)))
+ return result;
+
+ if (!getOrCreateChunk(n))
+ return nullptr;
+
+ // Since we just created a large enough chunk, this can't fail.
+ result = latest->tryAlloc(n);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ public:
+ explicit LifoAlloc(size_t defaultChunkSize)
+ : peakSize_(0)
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+ , fallibleScope_(true)
+#endif
+ {
+ reset(defaultChunkSize);
+ }
+
+ // Steal allocated chunks from |other|.
+ void steal(LifoAlloc* other) {
+ MOZ_ASSERT(!other->markCount);
+ MOZ_ASSERT(!latest);
+
+ // Copy everything from |other| to |this| except for |peakSize_|, which
+ // requires some care.
+ size_t oldPeakSize = peakSize_;
+ mozilla::PodAssign(this, other);
+ peakSize_ = Max(oldPeakSize, curSize_);
+
+ other->reset(defaultChunkSize_);
+ }
+
+ // Append all chunks from |other|. They are removed from |other|.
+ void transferFrom(LifoAlloc* other);
+
+ // Append unused chunks from |other|. They are removed from |other|.
+ void transferUnusedFrom(LifoAlloc* other);
+
+ ~LifoAlloc() { freeAll(); }
+
+ size_t defaultChunkSize() const { return defaultChunkSize_; }
+
+ // Frees all held memory.
+ void freeAll();
+
+ static const unsigned HUGE_ALLOCATION = 50 * 1024 * 1024;
+ void freeAllIfHugeAndUnused() {
+ if (markCount == 0 && curSize_ > HUGE_ALLOCATION)
+ freeAll();
+ }
+
+ MOZ_ALWAYS_INLINE
+ void* alloc(size_t n) {
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+ // Only simulate OOMs when we are not using the LifoAlloc as an
+ // infallible allocator.
+ if (fallibleScope_)
+ JS_OOM_POSSIBLY_FAIL();
+#endif
+ return allocImpl(n);
+ }
+
+ MOZ_ALWAYS_INLINE
+ void* allocInfallible(size_t n) {
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (void* result = allocImpl(n))
+ return result;
+ oomUnsafe.crash("LifoAlloc::allocInfallible");
+ return nullptr;
+ }
+
+ // Ensures that enough space exists to satisfy N bytes worth of
+ // allocation requests, not necessarily contiguous. Note that this does
+ // not guarantee a successful single allocation of N bytes.
+ MOZ_ALWAYS_INLINE
+ MOZ_MUST_USE bool ensureUnusedApproximate(size_t n) {
+ AutoFallibleScope fallibleAllocator(this);
+ size_t total = 0;
+ for (BumpChunk* chunk = latest; chunk; chunk = chunk->next()) {
+ total += chunk->unused();
+ if (total >= n)
+ return true;
+ }
+ BumpChunk* latestBefore = latest;
+ if (!getOrCreateChunk(n))
+ return false;
+ if (latestBefore)
+ latest = latestBefore;
+ return true;
+ }
+
+ MOZ_ALWAYS_INLINE
+ void setAsInfallibleByDefault() {
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+ fallibleScope_ = false;
+#endif
+ }
+
+ class MOZ_NON_TEMPORARY_CLASS AutoFallibleScope {
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+ LifoAlloc* lifoAlloc_;
+ bool prevFallibleScope_;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+ public:
+ explicit AutoFallibleScope(LifoAlloc* lifoAlloc MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ lifoAlloc_ = lifoAlloc;
+ prevFallibleScope_ = lifoAlloc->fallibleScope_;
+ lifoAlloc->fallibleScope_ = true;
+ }
+
+ ~AutoFallibleScope() {
+ lifoAlloc_->fallibleScope_ = prevFallibleScope_;
+ }
+#else
+ public:
+ explicit AutoFallibleScope(LifoAlloc*) {}
+#endif
+ };
+
+ template <typename T>
+ T* newArray(size_t count) {
+ static_assert(mozilla::IsPod<T>::value,
+ "T must be POD so that constructors (and destructors, "
+ "when the LifoAlloc is freed) need not be called");
+ return newArrayUninitialized<T>(count);
+ }
+
+ // Create an array with uninitialized elements of type |T|.
+ // The caller is responsible for initialization.
+ template <typename T>
+ T* newArrayUninitialized(size_t count) {
+ size_t bytes;
+ if (MOZ_UNLIKELY(!CalculateAllocSize<T>(count, &bytes)))
+ return nullptr;
+ return static_cast<T*>(alloc(bytes));
+ }
+
+ class Mark {
+ BumpChunk* chunk;
+ void* markInChunk;
+ friend class LifoAlloc;
+ Mark(BumpChunk* chunk, void* markInChunk) : chunk(chunk), markInChunk(markInChunk) {}
+ public:
+ Mark() : chunk(nullptr), markInChunk(nullptr) {}
+ };
+
+ Mark mark() {
+ markCount++;
+ return latest ? Mark(latest, latest->mark()) : Mark();
+ }
+
+ void release(Mark mark) {
+ markCount--;
+ if (!mark.chunk) {
+ latest = first;
+ if (latest)
+ latest->resetBump();
+ } else {
+ latest = mark.chunk;
+ latest->release(mark.markInChunk);
+ }
+ }
+
+ void releaseAll() {
+ MOZ_ASSERT(!markCount);
+ latest = first;
+ if (latest)
+ latest->resetBump();
+ }
+
+ // Get the total "used" (occupied bytes) count for the arena chunks.
+ size_t used() const {
+ size_t accum = 0;
+ for (BumpChunk* chunk = first; chunk; chunk = chunk->next()) {
+ accum += chunk->used();
+ if (chunk == latest)
+ break;
+ }
+ return accum;
+ }
+
+ // Return true if the LifoAlloc does not currently contain any allocations.
+ bool isEmpty() const {
+ return !latest || !latest->used();
+ }
+
+ // Return the number of bytes remaining to allocate in the current chunk.
+ // e.g. How many bytes we can allocate before needing a new block.
+ size_t availableInCurrentChunk() const {
+ if (!latest)
+ return 0;
+ return latest->unused();
+ }
+
+ // Get the total size of the arena chunks (including unused space).
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t n = 0;
+ for (BumpChunk* chunk = first; chunk; chunk = chunk->next())
+ n += chunk->sizeOfIncludingThis(mallocSizeOf);
+ return n;
+ }
+
+ // Get the total size of the arena chunks (including unused space).
+ size_t computedSizeOfExcludingThis() const {
+ size_t n = 0;
+ for (BumpChunk* chunk = first; chunk; chunk = chunk->next())
+ n += chunk->computedSizeOfIncludingThis();
+ return n;
+ }
+
+ // Like sizeOfExcludingThis(), but includes the size of the LifoAlloc itself.
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
+ }
+
+ // Get the peak size of the arena chunks (including unused space and
+ // bookkeeping space).
+ size_t peakSizeOfExcludingThis() const { return peakSize_; }
+
+ // Doesn't perform construction; useful for lazily-initialized POD types.
+ template <typename T>
+ MOZ_ALWAYS_INLINE
+ T* pod_malloc() {
+ return static_cast<T*>(alloc(sizeof(T)));
+ }
+
+ JS_DECLARE_NEW_METHODS(new_, alloc, MOZ_ALWAYS_INLINE)
+ JS_DECLARE_NEW_METHODS(newInfallible, allocInfallible, MOZ_ALWAYS_INLINE)
+
+#ifdef DEBUG
+ bool contains(void* ptr) const {
+ for (BumpChunk* chunk = first; chunk; chunk = chunk->next()) {
+ if (chunk->contains(ptr))
+ return true;
+ }
+ return false;
+ }
+#endif
+
+ // A mutable enumeration of the allocated data.
+ class Enum
+ {
+ friend class LifoAlloc;
+ friend class detail::BumpChunk;
+
+ LifoAlloc* alloc_; // The LifoAlloc being traversed.
+ BumpChunk* chunk_; // The current chunk.
+ char* position_; // The current position (must be within chunk_).
+
+ // If there is not enough room in the remaining block for |size|,
+ // advance to the next block and update the position.
+ void ensureSpaceAndAlignment(size_t size) {
+ MOZ_ASSERT(!empty());
+ char* aligned = detail::AlignPtr(position_);
+ if (aligned + size > chunk_->end()) {
+ chunk_ = chunk_->next();
+ position_ = static_cast<char*>(chunk_->start());
+ } else {
+ position_ = aligned;
+ }
+ MOZ_ASSERT(uintptr_t(position_) + size <= uintptr_t(chunk_->end()));
+ }
+
+ public:
+ explicit Enum(LifoAlloc& alloc)
+ : alloc_(&alloc),
+ chunk_(alloc.first),
+ position_(static_cast<char*>(alloc.first ? alloc.first->start() : nullptr))
+ {}
+
+ // Return true if there are no more bytes to enumerate.
+ bool empty() {
+ return !chunk_ || (chunk_ == alloc_->latest && position_ >= chunk_->mark());
+ }
+
+ // Move the read position forward by the size of one T.
+ template <typename T>
+ void popFront() {
+ popFront(sizeof(T));
+ }
+
+ // Move the read position forward by |size| bytes.
+ void popFront(size_t size) {
+ ensureSpaceAndAlignment(size);
+ position_ = position_ + size;
+ }
+
+ // Update the bytes at the current position with a new value.
+ template <typename T>
+ void updateFront(const T& t) {
+ ensureSpaceAndAlignment(sizeof(T));
+ memmove(position_, &t, sizeof(T));
+ }
+
+ // Return a pointer to the item at the current position. This
+ // returns a pointer to the inline storage, not a copy.
+ template <typename T>
+ T* get(size_t size = sizeof(T)) {
+ ensureSpaceAndAlignment(size);
+ return reinterpret_cast<T*>(position_);
+ }
+
+ // Return a Mark at the current position of the Enum.
+ Mark mark() {
+ alloc_->markCount++;
+ return Mark(chunk_, position_);
+ }
+ };
+};
+
+class MOZ_NON_TEMPORARY_CLASS LifoAllocScope
+{
+ LifoAlloc* lifoAlloc;
+ LifoAlloc::Mark mark;
+ LifoAlloc::AutoFallibleScope fallibleScope;
+ bool shouldRelease;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+ public:
+ explicit LifoAllocScope(LifoAlloc* lifoAlloc
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : lifoAlloc(lifoAlloc),
+ mark(lifoAlloc->mark()),
+ fallibleScope(lifoAlloc),
+ shouldRelease(true)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ }
+
+ ~LifoAllocScope() {
+ if (shouldRelease)
+ lifoAlloc->release(mark);
+ }
+
+ LifoAlloc& alloc() {
+ return *lifoAlloc;
+ }
+
+ void releaseEarly() {
+ MOZ_ASSERT(shouldRelease);
+ lifoAlloc->release(mark);
+ shouldRelease = false;
+ }
+};
+
+enum Fallibility {
+ Fallible,
+ Infallible
+};
+
+template <Fallibility fb>
+class LifoAllocPolicy
+{
+ LifoAlloc& alloc_;
+
+ public:
+ MOZ_IMPLICIT LifoAllocPolicy(LifoAlloc& alloc)
+ : alloc_(alloc)
+ {}
+ template <typename T>
+ T* maybe_pod_malloc(size_t numElems) {
+ size_t bytes;
+ if (MOZ_UNLIKELY(!CalculateAllocSize<T>(numElems, &bytes)))
+ return nullptr;
+ void* p = fb == Fallible ? alloc_.alloc(bytes) : alloc_.allocInfallible(bytes);
+ return static_cast<T*>(p);
+ }
+ template <typename T>
+ T* maybe_pod_calloc(size_t numElems) {
+ T* p = maybe_pod_malloc<T>(numElems);
+ if (MOZ_UNLIKELY(!p))
+ return nullptr;
+ memset(p, 0, numElems * sizeof(T));
+ return p;
+ }
+ template <typename T>
+ T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) {
+ T* n = maybe_pod_malloc<T>(newSize);
+ if (MOZ_UNLIKELY(!n))
+ return nullptr;
+ MOZ_ASSERT(!(oldSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value));
+ memcpy(n, p, Min(oldSize * sizeof(T), newSize * sizeof(T)));
+ return n;
+ }
+ template <typename T>
+ T* pod_malloc(size_t numElems) {
+ return maybe_pod_malloc<T>(numElems);
+ }
+ template <typename T>
+ T* pod_calloc(size_t numElems) {
+ return maybe_pod_calloc<T>(numElems);
+ }
+ template <typename T>
+ T* pod_realloc(T* p, size_t oldSize, size_t newSize) {
+ return maybe_pod_realloc<T>(p, oldSize, newSize);
+ }
+ void free_(void* p) {
+ }
+ void reportAllocOverflow() const {
+ }
+ MOZ_MUST_USE bool checkSimulatedOOM() const {
+ return fb == Infallible || !js::oom::ShouldFailWithOOM();
+ }
+};
+
+} // namespace js
+
+#endif /* ds_LifoAlloc_h */