#ifndef gc_StoreBuffer_h
#define gc_StoreBuffer_h
#include "mozilla/Attributes.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/ReentrancyGuard.h"
#include <algorithm>
#include "ds/BitArray.h"
#include "ds/LifoAlloc.h"
#include "gc/Nursery.h"
#include "js/AllocPolicy.h"
#include "js/MemoryMetrics.h"
#include "js/UniquePtr.h"
namespace js {
namespace gc {
class Arena;
class ArenaCellSet;
class BufferableRef {
public:
virtual void trace(JSTracer* trc) = 0;
bool maybeInRememberedSet(const Nursery&) const { return true; }
};
typedef HashSet<void*, PointerHasher<void*>, SystemAllocPolicy> EdgeSet;
static const size_t LifoAllocBlockSize = 8 * 1024;
class StoreBuffer {
friend class mozilla::ReentrancyGuard;
static const size_t GenericBufferLowAvailableThreshold =
LifoAllocBlockSize / 2;
static const size_t WholeCellBufferOverflowThresholdBytes = 128 * 1024;
template <typename T>
struct MonoTypeBuffer {
typedef HashSet<T, typename T::Hasher, SystemAllocPolicy> StoreSet;
StoreSet stores_;
T last_;
StoreBuffer* owner_;
const static size_t MaxEntries = 48 * 1024 / sizeof(T);
explicit MonoTypeBuffer(StoreBuffer* owner) : last_(T()), owner_(owner) {}
void clear() {
last_ = T();
stores_.clear();
}
void put(const T& t) {
sinkStore();
last_ = t;
}
void unput(const T& v) {
if (last_ == v) {
last_ = T();
return;
}
stores_.remove(v);
}
void sinkStore() {
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(T::FullBufferReason);
}
}
void trace(TenuringTracer& mover);
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
return stores_.shallowSizeOfExcludingThis(mallocSizeOf);
}
bool isEmpty() const { return last_ == T() && stores_.empty(); }
private:
MonoTypeBuffer(const MonoTypeBuffer& other) = delete;
MonoTypeBuffer& operator=(const MonoTypeBuffer& other) = delete;
};
struct WholeCellBuffer {
UniquePtr<LifoAlloc> storage_;
ArenaCellSet* head_;
StoreBuffer* owner_;
explicit WholeCellBuffer(StoreBuffer* owner)
: storage_(nullptr), head_(nullptr), owner_(owner) {}
MOZ_MUST_USE bool init();
void clear();
bool isAboutToOverflow() const {
return !storage_->isEmpty() &&
storage_->used() > WholeCellBufferOverflowThresholdBytes;
}
void trace(TenuringTracer& mover);
inline void put(const Cell* cell);
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
return storage_ ? storage_->sizeOfIncludingThis(mallocSizeOf) : 0;
}
bool isEmpty() const {
MOZ_ASSERT_IF(!head_, !storage_ || storage_->isEmpty());
return !head_;
}
private:
ArenaCellSet* allocateCellSet(Arena* arena);
WholeCellBuffer(const WholeCellBuffer& other) = delete;
WholeCellBuffer& operator=(const WholeCellBuffer& other) = delete;
};
struct GenericBuffer {
UniquePtr<LifoAlloc> storage_;
StoreBuffer* owner_;
explicit GenericBuffer(StoreBuffer* owner)
: storage_(nullptr), owner_(owner) {}
MOZ_MUST_USE bool init();
void clear() {
if (storage_) {
storage_->used() ? storage_->releaseAll() : storage_->freeAll();
}
}
bool isAboutToOverflow() const {
return !storage_->isEmpty() && storage_->availableInCurrentChunk() <
GenericBufferLowAvailableThreshold;
}
void trace(JSTracer* trc);
template <typename T>
void put(const T& t) {
MOZ_ASSERT(storage_);
(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(JS::GCReason::FULL_GENERIC_BUFFER);
}
}
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
return storage_ ? storage_->sizeOfIncludingThis(mallocSizeOf) : 0;
}
bool isEmpty() const { return !storage_ || storage_->isEmpty(); }
private:
GenericBuffer(const GenericBuffer& other) = delete;
GenericBuffer& operator=(const GenericBuffer& other) = delete;
};
template <typename Edge>
struct PointerEdgeHasher {
typedef Edge Lookup;
static HashNumber hash(const Lookup& l) {
return mozilla::HashGeneric(l.edge);
}
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;
static const auto FullBufferReason = JS::GCReason::FULL_CELL_PTR_BUFFER;
};
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;
static const auto FullBufferReason = JS::GCReason::FULL_VALUE_BUFFER;
};
struct SlotsEdge {
const static int SlotKind = 0;
const static int ElementKind = 1;
uintptr_t objectAndKind_; uint32_t start_;
uint32_t count_;
SlotsEdge() : objectAndKind_(0), start_(0), count_(0) {}
SlotsEdge(NativeObject* object, int kind, uint32_t start, uint32_t count)
: objectAndKind_(uintptr_t(object) | kind),
start_(start),
count_(count) {
MOZ_ASSERT((uintptr_t(object) & 1) == 0);
MOZ_ASSERT(kind <= 1);
MOZ_ASSERT(count > 0);
MOZ_ASSERT(start + count > start);
}
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); }
bool overlaps(const SlotsEdge& other) const {
if (objectAndKind_ != other.objectAndKind_) {
return false;
}
uint32_t end = start_ + count_ + 1;
uint32_t start = start_ > 0 ? start_ - 1 : 0;
MOZ_ASSERT(start < end);
uint32_t otherEnd = other.start_ + other.count_;
MOZ_ASSERT(other.start_ <= otherEnd);
return (start <= other.start_ && other.start_ <= end) ||
(start <= otherEnd && otherEnd <= end);
}
void merge(const SlotsEdge& other) {
MOZ_ASSERT(overlaps(other));
uint32_t 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 mozilla::HashGeneric(l.objectAndKind_, l.start_, l.count_);
}
static bool match(const SlotsEdge& k, const Lookup& l) { return k == l; }
} Hasher;
static const auto FullBufferReason = JS::GCReason::FULL_SLOT_BUFFER;
};
template <typename Buffer, typename Edge>
void unput(Buffer& buffer, const Edge& edge) {
MOZ_ASSERT(!JS::RuntimeHeapIsBusy());
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
if (!isEnabled()) {
return;
}
mozilla::ReentrancyGuard g(*this);
buffer.unput(edge);
}
template <typename Buffer, typename Edge>
void put(Buffer& buffer, const Edge& edge) {
MOZ_ASSERT(!JS::RuntimeHeapIsBusy());
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
if (!isEnabled()) {
return;
}
mozilla::ReentrancyGuard g(*this);
if (edge.maybeInRememberedSet(nursery_)) {
buffer.put(edge);
}
}
MonoTypeBuffer<ValueEdge> bufferVal;
MonoTypeBuffer<CellPtrEdge> bufferCell;
MonoTypeBuffer<SlotsEdge> bufferSlot;
WholeCellBuffer bufferWholeCell;
GenericBuffer bufferGeneric;
bool cancelIonCompilations_;
JSRuntime* runtime_;
const Nursery& nursery_;
bool aboutToOverflow_;
bool enabled_;
#ifdef DEBUG
bool mEntered;
#endif
public:
explicit StoreBuffer(JSRuntime* rt, const Nursery& nursery);
MOZ_MUST_USE bool enable();
void disable();
bool isEnabled() const { return enabled_; }
void clear();
const Nursery& nursery() const { return nursery_; }
bool isAboutToOverflow() const { return aboutToOverflow_; }
bool cancelIonCompilations() const { return cancelIonCompilations_; }
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, uint32_t start, uint32_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);
template <typename T>
void putGeneric(const T& t) {
put(bufferGeneric, t);
}
void setShouldCancelIonCompilations() { cancelIonCompilations_ = true; }
void traceValues(TenuringTracer& mover) { bufferVal.trace(mover); }
void traceCells(TenuringTracer& mover) { bufferCell.trace(mover); }
void traceSlots(TenuringTracer& mover) { bufferSlot.trace(mover); }
void traceWholeCells(TenuringTracer& mover) { bufferWholeCell.trace(mover); }
void traceGenericEntries(JSTracer* trc) { bufferGeneric.trace(trc); }
void setAboutToOverflow(JS::GCReason);
void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
JS::GCSizes* sizes);
void checkEmpty() const;
};
class ArenaCellSet {
friend class StoreBuffer;
Arena* arena;
ArenaCellSet* next;
BitArray<MaxArenaCellIndex> bits;
#ifdef DEBUG
const uint64_t minorGCNumberAtCreation;
#endif
constexpr ArenaCellSet()
: arena(nullptr),
next(nullptr)
#ifdef DEBUG
,
minorGCNumberAtCreation(0)
#endif
{
}
public:
ArenaCellSet(Arena* arena, ArenaCellSet* next);
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;
static ArenaCellSet Empty;
static size_t getCellIndex(const TenuredCell* cell);
static void getWordIndexAndMask(size_t cellIndex, size_t* wordp,
uint32_t* maskp);
static const size_t NurseryFreeThresholdBytes = 64 * 1024;
static size_t offsetOfArena() { return offsetof(ArenaCellSet, arena); }
static size_t offsetOfBits() { return offsetof(ArenaCellSet, bits); }
};
}
}
#endif