diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /js/src/gc/StoreBuffer.h | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/gc/StoreBuffer.h')
-rw-r--r-- | js/src/gc/StoreBuffer.h | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/js/src/gc/StoreBuffer.h b/js/src/gc/StoreBuffer.h new file mode 100644 index 000000000..f8d9031f1 --- /dev/null +++ b/js/src/gc/StoreBuffer.h @@ -0,0 +1,499 @@ +/* -*- 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 gc_StoreBuffer_h +#define gc_StoreBuffer_h + +#include "mozilla/Attributes.h" +#include "mozilla/ReentrancyGuard.h" + +#include <algorithm> + +#include "jsalloc.h" + +#include "ds/LifoAlloc.h" +#include "gc/Nursery.h" +#include "js/MemoryMetrics.h" + +namespace js { +namespace gc { + +class ArenaCellSet; + +/* + * BufferableRef represents an abstract reference for use in the generational + * GC's remembered set. Entries in the store buffer that cannot be represented + * with the simple pointer-to-a-pointer scheme must derive from this class and + * use the generic store buffer interface. + * + * A single BufferableRef entry in the generic buffer can represent many entries + * in the remembered set. For example js::OrderedHashTableRef represents all + * the incoming edges corresponding to keys in an ordered hash table. + */ +class BufferableRef +{ + public: + virtual void trace(JSTracer* trc) = 0; + bool maybeInRememberedSet(const Nursery&) const { return true; } +}; + +typedef HashSet<void*, PointerHasher<void*, 3>, SystemAllocPolicy> EdgeSet; + +/* The size of a single block of store buffer storage space. */ +static const size_t LifoAllocBlockSize = 1 << 13; /* 8KiB */ + +/* + * The StoreBuffer observes all writes that occur in the system and performs + * efficient filtering of them to derive a remembered set for nursery GC. + */ +class StoreBuffer +{ + friend class mozilla::ReentrancyGuard; + + /* The size at which a block is about to overflow. */ + static const size_t LowAvailableThreshold = size_t(LifoAllocBlockSize / 2.0); + + /* + * This buffer holds only a single type of edge. Using this buffer is more + * efficient than the generic buffer when many writes will be to the same + * type of edge: e.g. Value or Cell*. + */ + template<typename T> + struct MonoTypeBuffer + { + /* The canonical set of stores. */ + typedef HashSet<T, typename T::Hasher, SystemAllocPolicy> StoreSet; + StoreSet stores_; + + /* + * A one element cache in front of the canonical set to speed up + * temporary instances of HeapPtr. + */ + T last_; + + /* Maximum number of entries before we request a minor GC. */ + const static size_t MaxEntries = 48 * 1024 / sizeof(T); + + explicit MonoTypeBuffer() : last_(T()) {} + ~MonoTypeBuffer() { stores_.finish(); } + + MOZ_MUST_USE bool init() { + if (!stores_.initialized() && !stores_.init()) + return false; + clear(); + return true; + } + + void clear() { + last_ = T(); + if (stores_.initialized()) + stores_.clear(); + } + + /* Add one item to the buffer. */ + void put(StoreBuffer* owner, const T& t) { + MOZ_ASSERT(stores_.initialized()); + sinkStore(owner); + last_ = t; + } + + /* Remove an item from the store buffer. */ + void unput(StoreBuffer* owner, const T& v) { + // Fast, hashless remove of last put. + if (last_ == v) { + last_ = T(); + return; + } + stores_.remove(v); + } + + /* Move any buffered stores to the canonical store set. */ + void sinkStore(StoreBuffer* owner) { + MOZ_ASSERT(stores_.initialized()); + if (last_) { + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!stores_.put(last_)) + oomUnsafe.crash("Failed to allocate for MonoTypeBuffer::put."); + } + last_ = T(); + + if (MOZ_UNLIKELY(stores_.count() > MaxEntries)) + owner->setAboutToOverflow(); + } + + bool has(StoreBuffer* owner, const T& v) { + sinkStore(owner); + return stores_.has(v); + } + + /* Trace the source of all edges in the store buffer. */ + void trace(StoreBuffer* owner, TenuringTracer& mover); + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { + return stores_.sizeOfExcludingThis(mallocSizeOf); + } + + private: + MonoTypeBuffer& operator=(const MonoTypeBuffer& other) = delete; + }; + + struct GenericBuffer + { + LifoAlloc* storage_; + + explicit GenericBuffer() : storage_(nullptr) {} + ~GenericBuffer() { js_delete(storage_); } + + MOZ_MUST_USE bool init() { + if (!storage_) + storage_ = js_new<LifoAlloc>(LifoAllocBlockSize); + clear(); + return bool(storage_); + } + + void clear() { + if (!storage_) + return; + + storage_->used() ? storage_->releaseAll() : storage_->freeAll(); + } + + bool isAboutToOverflow() const { + return !storage_->isEmpty() && + storage_->availableInCurrentChunk() < LowAvailableThreshold; + } + + /* Trace all generic edges. */ + void trace(StoreBuffer* owner, JSTracer* trc); + + template <typename T> + void put(StoreBuffer* owner, const T& t) { + MOZ_ASSERT(storage_); + + /* Ensure T is derived from BufferableRef. */ + (void)static_cast<const BufferableRef*>(&t); + + AutoEnterOOMUnsafeRegion oomUnsafe; + unsigned size = sizeof(T); + unsigned* sizep = storage_->pod_malloc<unsigned>(); + if (!sizep) + oomUnsafe.crash("Failed to allocate for GenericBuffer::put."); + *sizep = size; + + T* tp = storage_->new_<T>(t); + if (!tp) + oomUnsafe.crash("Failed to allocate for GenericBuffer::put."); + + if (isAboutToOverflow()) + owner->setAboutToOverflow(); + } + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { + return storage_ ? storage_->sizeOfIncludingThis(mallocSizeOf) : 0; + } + + bool isEmpty() { + return !storage_ || storage_->isEmpty(); + } + + private: + GenericBuffer& operator=(const GenericBuffer& other) = delete; + }; + + template <typename Edge> + struct PointerEdgeHasher + { + typedef Edge Lookup; + static HashNumber hash(const Lookup& l) { return uintptr_t(l.edge) >> 3; } + static bool match(const Edge& k, const Lookup& l) { return k == l; } + }; + + struct CellPtrEdge + { + Cell** edge; + + CellPtrEdge() : edge(nullptr) {} + explicit CellPtrEdge(Cell** v) : edge(v) {} + bool operator==(const CellPtrEdge& other) const { return edge == other.edge; } + bool operator!=(const CellPtrEdge& other) const { return edge != other.edge; } + + bool maybeInRememberedSet(const Nursery& nursery) const { + MOZ_ASSERT(IsInsideNursery(*edge)); + return !nursery.isInside(edge); + } + + void trace(TenuringTracer& mover) const; + + CellPtrEdge tagged() const { return CellPtrEdge((Cell**)(uintptr_t(edge) | 1)); } + CellPtrEdge untagged() const { return CellPtrEdge((Cell**)(uintptr_t(edge) & ~1)); } + bool isTagged() const { return bool(uintptr_t(edge) & 1); } + + explicit operator bool() const { return edge != nullptr; } + + typedef PointerEdgeHasher<CellPtrEdge> Hasher; + }; + + struct ValueEdge + { + JS::Value* edge; + + ValueEdge() : edge(nullptr) {} + explicit ValueEdge(JS::Value* v) : edge(v) {} + bool operator==(const ValueEdge& other) const { return edge == other.edge; } + bool operator!=(const ValueEdge& other) const { return edge != other.edge; } + + Cell* deref() const { return edge->isGCThing() ? static_cast<Cell*>(edge->toGCThing()) : nullptr; } + + bool maybeInRememberedSet(const Nursery& nursery) const { + MOZ_ASSERT(IsInsideNursery(deref())); + return !nursery.isInside(edge); + } + + void trace(TenuringTracer& mover) const; + + ValueEdge tagged() const { return ValueEdge((JS::Value*)(uintptr_t(edge) | 1)); } + ValueEdge untagged() const { return ValueEdge((JS::Value*)(uintptr_t(edge) & ~1)); } + bool isTagged() const { return bool(uintptr_t(edge) & 1); } + + explicit operator bool() const { return edge != nullptr; } + + typedef PointerEdgeHasher<ValueEdge> Hasher; + }; + + struct SlotsEdge + { + // These definitions must match those in HeapSlot::Kind. + const static int SlotKind = 0; + const static int ElementKind = 1; + + uintptr_t objectAndKind_; // NativeObject* | Kind + int32_t start_; + int32_t count_; + + SlotsEdge() : objectAndKind_(0), start_(0), count_(0) {} + SlotsEdge(NativeObject* object, int kind, int32_t start, int32_t count) + : objectAndKind_(uintptr_t(object) | kind), start_(start), count_(count) + { + MOZ_ASSERT((uintptr_t(object) & 1) == 0); + MOZ_ASSERT(kind <= 1); + MOZ_ASSERT(start >= 0); + MOZ_ASSERT(count > 0); + } + + NativeObject* object() const { return reinterpret_cast<NativeObject*>(objectAndKind_ & ~1); } + int kind() const { return (int)(objectAndKind_ & 1); } + + bool operator==(const SlotsEdge& other) const { + return objectAndKind_ == other.objectAndKind_ && + start_ == other.start_ && + count_ == other.count_; + } + + bool operator!=(const SlotsEdge& other) const { + return !(*this == other); + } + + // True if this SlotsEdge range overlaps with the other SlotsEdge range, + // false if they do not overlap. + bool overlaps(const SlotsEdge& other) const { + if (objectAndKind_ != other.objectAndKind_) + return false; + + // Widen our range by one on each side so that we consider + // adjacent-but-not-actually-overlapping ranges as overlapping. This + // is particularly useful for coalescing a series of increasing or + // decreasing single index writes 0, 1, 2, ..., N into a SlotsEdge + // range of elements [0, N]. + auto end = start_ + count_ + 1; + auto start = start_ - 1; + + auto otherEnd = other.start_ + other.count_; + return (start <= other.start_ && other.start_ <= end) || + (start <= otherEnd && otherEnd <= end); + } + + // Destructively make this SlotsEdge range the union of the other + // SlotsEdge range and this one. A precondition is that the ranges must + // overlap. + void merge(const SlotsEdge& other) { + MOZ_ASSERT(overlaps(other)); + auto end = Max(start_ + count_, other.start_ + other.count_); + start_ = Min(start_, other.start_); + count_ = end - start_; + } + + bool maybeInRememberedSet(const Nursery& n) const { + return !IsInsideNursery(reinterpret_cast<Cell*>(object())); + } + + void trace(TenuringTracer& mover) const; + + explicit operator bool() const { return objectAndKind_ != 0; } + + typedef struct { + typedef SlotsEdge Lookup; + static HashNumber hash(const Lookup& l) { return l.objectAndKind_ ^ l.start_ ^ l.count_; } + static bool match(const SlotsEdge& k, const Lookup& l) { return k == l; } + } Hasher; + }; + + template <typename Buffer, typename Edge> + void unput(Buffer& buffer, const Edge& edge) { + MOZ_ASSERT(!JS::shadow::Runtime::asShadowRuntime(runtime_)->isHeapBusy()); + MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); + if (!isEnabled()) + return; + mozilla::ReentrancyGuard g(*this); + buffer.unput(this, edge); + } + + template <typename Buffer, typename Edge> + void put(Buffer& buffer, const Edge& edge) { + MOZ_ASSERT(!JS::shadow::Runtime::asShadowRuntime(runtime_)->isHeapBusy()); + MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); + if (!isEnabled()) + return; + mozilla::ReentrancyGuard g(*this); + if (edge.maybeInRememberedSet(nursery_)) + buffer.put(this, edge); + } + + MonoTypeBuffer<ValueEdge> bufferVal; + MonoTypeBuffer<CellPtrEdge> bufferCell; + MonoTypeBuffer<SlotsEdge> bufferSlot; + ArenaCellSet* bufferWholeCell; + GenericBuffer bufferGeneric; + bool cancelIonCompilations_; + + JSRuntime* runtime_; + const Nursery& nursery_; + + bool aboutToOverflow_; + bool enabled_; +#ifdef DEBUG + bool mEntered; /* For ReentrancyGuard. */ +#endif + + public: + explicit StoreBuffer(JSRuntime* rt, const Nursery& nursery) + : bufferVal(), bufferCell(), bufferSlot(), bufferWholeCell(nullptr), bufferGeneric(), + cancelIonCompilations_(false), runtime_(rt), nursery_(nursery), aboutToOverflow_(false), + enabled_(false) +#ifdef DEBUG + , mEntered(false) +#endif + { + } + + MOZ_MUST_USE bool enable(); + void disable(); + bool isEnabled() const { return enabled_; } + + void clear(); + + /* Get the overflowed status. */ + bool isAboutToOverflow() const { return aboutToOverflow_; } + + bool cancelIonCompilations() const { return cancelIonCompilations_; } + + /* Insert a single edge into the buffer/remembered set. */ + void putValue(JS::Value* vp) { put(bufferVal, ValueEdge(vp)); } + void unputValue(JS::Value* vp) { unput(bufferVal, ValueEdge(vp)); } + void putCell(Cell** cellp) { put(bufferCell, CellPtrEdge(cellp)); } + void unputCell(Cell** cellp) { unput(bufferCell, CellPtrEdge(cellp)); } + void putSlot(NativeObject* obj, int kind, int32_t start, int32_t count) { + SlotsEdge edge(obj, kind, start, count); + if (bufferSlot.last_.overlaps(edge)) + bufferSlot.last_.merge(edge); + else + put(bufferSlot, edge); + } + inline void putWholeCell(Cell* cell); + + /* Insert an entry into the generic buffer. */ + template <typename T> + void putGeneric(const T& t) { put(bufferGeneric, t);} + + void setShouldCancelIonCompilations() { + cancelIonCompilations_ = true; + } + + /* Methods to trace the source of all edges in the store buffer. */ + void traceValues(TenuringTracer& mover) { bufferVal.trace(this, mover); } + void traceCells(TenuringTracer& mover) { bufferCell.trace(this, mover); } + void traceSlots(TenuringTracer& mover) { bufferSlot.trace(this, mover); } + void traceGenericEntries(JSTracer *trc) { bufferGeneric.trace(this, trc); } + + void traceWholeCells(TenuringTracer& mover); + void traceWholeCell(TenuringTracer& mover, JS::TraceKind kind, Cell* cell); + + /* For use by our owned buffers and for testing. */ + void setAboutToOverflow(); + + void addToWholeCellBuffer(ArenaCellSet* set); + + void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::GCSizes* sizes); +}; + +// A set of cells in an arena used to implement the whole cell store buffer. +class ArenaCellSet +{ + friend class StoreBuffer; + + // The arena this relates to. + Arena* arena; + + // Pointer to next set forming a linked list. + ArenaCellSet* next; + + // Bit vector for each possible cell start position. + BitArray<ArenaCellCount> bits; + + public: + explicit ArenaCellSet(Arena* arena); + + bool hasCell(const TenuredCell* cell) const { + return hasCell(getCellIndex(cell)); + } + + void putCell(const TenuredCell* cell) { + putCell(getCellIndex(cell)); + } + + bool isEmpty() const { + return this == &Empty; + } + + bool hasCell(size_t cellIndex) const; + + void putCell(size_t cellIndex); + + void check() const; + + // Sentinel object used for all empty sets. + static ArenaCellSet Empty; + + static size_t getCellIndex(const TenuredCell* cell); + static void getWordIndexAndMask(size_t cellIndex, size_t* wordp, uint32_t* maskp); + + // Attempt to trigger a minor GC if free space in the nursery (where these + // objects are allocated) falls below this threshold. + static const size_t NurseryFreeThresholdBytes = 64 * 1024; + + static size_t offsetOfArena() { + return offsetof(ArenaCellSet, arena); + } + static size_t offsetOfBits() { + return offsetof(ArenaCellSet, bits); + } +}; + +ArenaCellSet* AllocateWholeCellSet(Arena* arena); + +} /* namespace gc */ +} /* namespace js */ + +#endif /* gc_StoreBuffer_h */ |