#ifndef gc_Heap_h
#define gc_Heap_h
#include "mozilla/ArrayUtils.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/PodOperations.h"
#include <stddef.h>
#include <stdint.h>
#include "jsfriendapi.h"
#include "jspubtd.h"
#include "jstypes.h"
#include "jsutil.h"
#include "ds/BitArray.h"
#include "gc/AllocKind.h"
#include "gc/GCEnum.h"
#include "gc/Memory.h"
#include "js/HeapAPI.h"
#include "js/RootingAPI.h"
#include "js/TracingAPI.h"
#include "js/TypeDecls.h"
#include "vm/Printer.h"
namespace js {
class AutoLockGC;
class AutoLockGCBgAlloc;
class FreeOp;
namespace gc {
class Arena;
class ArenaCellSet;
class ArenaList;
class SortedArenaList;
class StoreBuffer;
class TenuredCell;
struct Chunk;
enum InitialHeap : uint8_t { DefaultHeap, TenuredHeap };
const uintptr_t LargestTaggedNullCellPointer = (1 << CellAlignShift) - 1;
const size_t MarkBitsPerCell = 2;
const size_t MinCellSize = CellBytesPerMarkBit * MarkBitsPerCell;
constexpr size_t DivideAndRoundUp(size_t numerator, size_t divisor) {
return (numerator + divisor - 1) / divisor;
}
static_assert(ArenaSize % CellAlignBytes == 0,
"Arena size must be a multiple of cell alignment");
const size_t ArenaBitmapBits = ArenaSize / CellBytesPerMarkBit;
const size_t ArenaBitmapBytes = DivideAndRoundUp(ArenaBitmapBits, 8);
const size_t ArenaBitmapWords =
DivideAndRoundUp(ArenaBitmapBits, JS_BITS_PER_WORD);
class FreeSpan {
friend class Arena;
friend class ArenaCellIterImpl;
friend class ArenaFreeCellIter;
uint16_t first;
uint16_t last;
public:
void initBounds(uintptr_t firstArg, uintptr_t lastArg, const Arena* arena) {
checkRange(firstArg, lastArg, arena);
first = firstArg;
last = lastArg;
}
void initAsEmpty() {
first = 0;
last = 0;
}
void initFinal(uintptr_t firstArg, uintptr_t lastArg, const Arena* arena) {
initBounds(firstArg, lastArg, arena);
FreeSpan* last = nextSpanUnchecked(arena);
last->initAsEmpty();
checkSpan(arena);
}
bool isEmpty() const { return !first; }
Arena* getArenaUnchecked() { return reinterpret_cast<Arena*>(this); }
inline Arena* getArena();
static size_t offsetOfFirst() { return offsetof(FreeSpan, first); }
static size_t offsetOfLast() { return offsetof(FreeSpan, last); }
FreeSpan* nextSpanUnchecked(const Arena* arena) const {
MOZ_ASSERT(arena && !isEmpty());
return reinterpret_cast<FreeSpan*>(uintptr_t(arena) + last);
}
const FreeSpan* nextSpan(const Arena* arena) const {
checkSpan(arena);
return nextSpanUnchecked(arena);
}
MOZ_ALWAYS_INLINE TenuredCell* allocate(size_t thingSize) {
Arena* arena = getArenaUnchecked();
checkSpan(arena);
uintptr_t thing = uintptr_t(arena) + first;
if (first < last) {
first += thingSize;
} else if (MOZ_LIKELY(first)) {
const FreeSpan* next = nextSpan(arena);
first = next->first;
last = next->last;
} else {
return nullptr; }
checkSpan(arena);
DebugOnlyPoison(reinterpret_cast<void*>(thing),
JS_ALLOCATED_TENURED_PATTERN, thingSize,
MemCheckKind::MakeUndefined);
return reinterpret_cast<TenuredCell*>(thing);
}
inline void checkSpan(const Arena* arena) const;
inline void checkRange(uintptr_t first, uintptr_t last,
const Arena* arena) const;
};
class Arena {
static JS_FRIEND_DATA const uint32_t ThingSizes[];
static JS_FRIEND_DATA const uint32_t FirstThingOffsets[];
static JS_FRIEND_DATA const uint32_t ThingsPerArena[];
FreeSpan firstFreeSpan;
public:
JS::Zone* zone;
Arena* next;
private:
size_t allocKind : 8;
private:
static const size_t DELAYED_MARKING_FLAG_BITS = 3;
static const size_t DELAYED_MARKING_ARENA_BITS =
JS_BITS_PER_WORD - 8 - DELAYED_MARKING_FLAG_BITS;
size_t onDelayedMarkingList_ : 1;
size_t hasDelayedBlackMarking_ : 1;
size_t hasDelayedGrayMarking_ : 1;
size_t nextDelayedMarkingArena_ : DELAYED_MARKING_ARENA_BITS;
static_assert(
DELAYED_MARKING_ARENA_BITS >= JS_BITS_PER_WORD - ArenaShift,
"Arena::nextDelayedMarkingArena_ packing assumes that ArenaShift has "
"enough bits to cover allocKind and delayed marking state.");
union {
ArenaCellSet* bufferedCells_;
size_t atomBitmapStart_;
};
public:
uint8_t data[ArenaSize - ArenaHeaderSize];
void init(JS::Zone* zoneArg, AllocKind kind, const AutoLockGC& lock);
void setAsFullyUnused() {
AllocKind kind = getAllocKind();
firstFreeSpan.first = firstThingOffset(kind);
firstFreeSpan.last = lastThingOffset(kind);
FreeSpan* last = firstFreeSpan.nextSpanUnchecked(this);
last->initAsEmpty();
}
void setAsNotAllocated() {
firstFreeSpan.initAsEmpty();
zone = nullptr;
allocKind = size_t(AllocKind::LIMIT);
onDelayedMarkingList_ = 0;
hasDelayedBlackMarking_ = 0;
hasDelayedGrayMarking_ = 0;
nextDelayedMarkingArena_ = 0;
bufferedCells_ = nullptr;
}
inline void release(const AutoLockGC& lock);
uintptr_t address() const {
checkAddress();
return uintptr_t(this);
}
inline void checkAddress() const;
inline Chunk* chunk() const;
bool allocated() const {
MOZ_ASSERT(IsAllocKind(AllocKind(allocKind)));
return IsValidAllocKind(AllocKind(allocKind));
}
AllocKind getAllocKind() const {
MOZ_ASSERT(allocated());
return AllocKind(allocKind);
}
FreeSpan* getFirstFreeSpan() { return &firstFreeSpan; }
static size_t thingSize(AllocKind kind) { return ThingSizes[size_t(kind)]; }
static size_t thingsPerArena(AllocKind kind) {
return ThingsPerArena[size_t(kind)];
}
static size_t thingsSpan(AllocKind kind) {
return thingsPerArena(kind) * thingSize(kind);
}
static size_t firstThingOffset(AllocKind kind) {
return FirstThingOffsets[size_t(kind)];
}
static size_t lastThingOffset(AllocKind kind) {
return ArenaSize - thingSize(kind);
}
size_t getThingSize() const { return thingSize(getAllocKind()); }
size_t getThingsPerArena() const { return thingsPerArena(getAllocKind()); }
size_t getThingsSpan() const { return getThingsPerArena() * getThingSize(); }
size_t getFirstThingOffset() const {
return firstThingOffset(getAllocKind());
}
uintptr_t thingsStart() const { return address() + getFirstThingOffset(); }
uintptr_t thingsEnd() const { return address() + ArenaSize; }
bool isEmpty() const {
firstFreeSpan.checkSpan(this);
AllocKind kind = getAllocKind();
return firstFreeSpan.first == firstThingOffset(kind) &&
firstFreeSpan.last == lastThingOffset(kind);
}
bool hasFreeThings() const { return !firstFreeSpan.isEmpty(); }
size_t numFreeThings(size_t thingSize) const {
firstFreeSpan.checkSpan(this);
size_t numFree = 0;
const FreeSpan* span = &firstFreeSpan;
for (; !span->isEmpty(); span = span->nextSpan(this)) {
numFree += (span->last - span->first) / thingSize + 1;
}
return numFree;
}
size_t countFreeCells() { return numFreeThings(getThingSize()); }
size_t countUsedCells() { return getThingsPerArena() - countFreeCells(); }
bool inFreeList(uintptr_t thing) {
uintptr_t base = address();
const FreeSpan* span = &firstFreeSpan;
for (; !span->isEmpty(); span = span->nextSpan(this)) {
if (thing < base + span->first) {
return false;
}
if (thing <= base + span->last) {
return true;
}
}
return false;
}
static bool isAligned(uintptr_t thing, size_t thingSize) {
uintptr_t tailOffset = ArenaSize - (thing & ArenaMask);
return tailOffset % thingSize == 0;
}
bool onDelayedMarkingList() const { return onDelayedMarkingList_; }
Arena* getNextDelayedMarking() const {
MOZ_ASSERT(onDelayedMarkingList_);
return reinterpret_cast<Arena*>(nextDelayedMarkingArena_ << ArenaShift);
}
void setNextDelayedMarkingArena(Arena* arena) {
MOZ_ASSERT(!(uintptr_t(arena) & ArenaMask));
MOZ_ASSERT(!onDelayedMarkingList_);
MOZ_ASSERT(!hasDelayedBlackMarking_);
MOZ_ASSERT(!hasDelayedGrayMarking_);
MOZ_ASSERT(!nextDelayedMarkingArena_);
onDelayedMarkingList_ = 1;
if (arena) {
nextDelayedMarkingArena_ = arena->address() >> ArenaShift;
}
}
void updateNextDelayedMarkingArena(Arena* arena) {
MOZ_ASSERT(!(uintptr_t(arena) & ArenaMask));
MOZ_ASSERT(onDelayedMarkingList_);
nextDelayedMarkingArena_ = arena ? arena->address() >> ArenaShift : 0;
}
bool hasDelayedMarking(MarkColor color) const {
MOZ_ASSERT(onDelayedMarkingList_);
return color == MarkColor::Black ? hasDelayedBlackMarking_
: hasDelayedGrayMarking_;
}
bool hasAnyDelayedMarking() const {
MOZ_ASSERT(onDelayedMarkingList_);
return hasDelayedBlackMarking_ || hasDelayedGrayMarking_;
}
void setHasDelayedMarking(MarkColor color, bool value) {
MOZ_ASSERT(onDelayedMarkingList_);
if (color == MarkColor::Black) {
hasDelayedBlackMarking_ = value;
} else {
hasDelayedGrayMarking_ = value;
}
}
void clearDelayedMarkingState() {
MOZ_ASSERT(onDelayedMarkingList_);
onDelayedMarkingList_ = 0;
hasDelayedBlackMarking_ = 0;
hasDelayedGrayMarking_ = 0;
nextDelayedMarkingArena_ = 0;
}
inline ArenaCellSet*& bufferedCells();
inline size_t& atomBitmapStart();
template <typename T>
size_t finalize(FreeOp* fop, AllocKind thingKind, size_t thingSize);
static void staticAsserts();
void unmarkAll();
void unmarkPreMarkedFreeCells();
void arenaAllocatedDuringGC();
#ifdef DEBUG
void checkNoMarkedFreeCells();
#endif
};
static_assert(ArenaZoneOffset == offsetof(Arena, zone),
"The hardcoded API zone offset must match the actual offset.");
static_assert(sizeof(Arena) == ArenaSize,
"ArenaSize must match the actual size of the Arena structure.");
static_assert(
offsetof(Arena, data) == ArenaHeaderSize,
"ArenaHeaderSize must match the actual size of the header fields.");
inline Arena* FreeSpan::getArena() {
Arena* arena = getArenaUnchecked();
arena->checkAddress();
return arena;
}
inline void FreeSpan::checkSpan(const Arena* arena) const {
#ifdef DEBUG
if (!first) {
MOZ_ASSERT(!first && !last);
return;
}
arena->checkAddress();
checkRange(first, last, arena);
const FreeSpan* next = nextSpanUnchecked(arena);
if (next->first) {
checkRange(next->first, next->last, arena);
size_t thingSize = arena->getThingSize();
MOZ_ASSERT(last + 2 * thingSize <= next->first);
}
#endif
}
inline void FreeSpan::checkRange(uintptr_t first, uintptr_t last,
const Arena* arena) const {
#ifdef DEBUG
MOZ_ASSERT(arena);
MOZ_ASSERT(first <= last);
AllocKind thingKind = arena->getAllocKind();
MOZ_ASSERT(first >= Arena::firstThingOffset(thingKind));
MOZ_ASSERT(last <= Arena::lastThingOffset(thingKind));
MOZ_ASSERT((last - first) % Arena::thingSize(thingKind) == 0);
#endif
}
struct ChunkTrailer {
ChunkTrailer(JSRuntime* rt, StoreBuffer* sb)
: location(ChunkLocation::Nursery), storeBuffer(sb), runtime(rt) {}
explicit ChunkTrailer(JSRuntime* rt)
: location(ChunkLocation::TenuredHeap),
storeBuffer(nullptr),
runtime(rt) {}
public:
ChunkLocation location;
uint32_t : 32;
StoreBuffer* storeBuffer;
JSRuntime* runtime;
};
static_assert(sizeof(ChunkTrailer) == ChunkTrailerSize,
"ChunkTrailer size must match the API defined size.");
struct ChunkInfo {
void init() { next = prev = nullptr; }
private:
friend class ChunkPool;
Chunk* next;
Chunk* prev;
public:
Arena* freeArenasHead;
#if JS_BITS_PER_WORD == 32
char padding[24];
#endif
uint32_t lastDecommittedArenaOffset;
uint32_t numArenasFree;
uint32_t numArenasFreeCommitted;
};
const size_t BytesPerArenaWithHeader = ArenaSize + ArenaBitmapBytes;
const size_t ChunkDecommitBitmapBytes = ChunkSize / ArenaSize / CHAR_BIT;
const size_t ChunkBytesAvailable = ChunkSize - sizeof(ChunkTrailer) -
sizeof(ChunkInfo) - ChunkDecommitBitmapBytes;
const size_t ArenasPerChunk = ChunkBytesAvailable / BytesPerArenaWithHeader;
#ifdef JS_GC_SMALL_CHUNK_SIZE
static_assert(ArenasPerChunk == 62,
"Do not accidentally change our heap's density.");
#else
static_assert(ArenasPerChunk == 252,
"Do not accidentally change our heap's density.");
#endif
struct ChunkBitmap {
volatile uintptr_t bitmap[ArenaBitmapWords * ArenasPerChunk];
public:
ChunkBitmap() {}
MOZ_ALWAYS_INLINE void getMarkWordAndMask(const TenuredCell* cell,
ColorBit colorBit,
uintptr_t** wordp,
uintptr_t* maskp) {
MOZ_ASSERT(size_t(colorBit) < MarkBitsPerCell);
detail::GetGCThingMarkWordAndMask(uintptr_t(cell), colorBit, wordp, maskp);
}
MOZ_ALWAYS_INLINE MOZ_TSAN_BLACKLIST bool markBit(const TenuredCell* cell,
ColorBit colorBit) {
uintptr_t *word, mask;
getMarkWordAndMask(cell, colorBit, &word, &mask);
return *word & mask;
}
MOZ_ALWAYS_INLINE MOZ_TSAN_BLACKLIST bool isMarkedAny(
const TenuredCell* cell) {
return markBit(cell, ColorBit::BlackBit) ||
markBit(cell, ColorBit::GrayOrBlackBit);
}
MOZ_ALWAYS_INLINE MOZ_TSAN_BLACKLIST bool isMarkedBlack(
const TenuredCell* cell) {
return markBit(cell, ColorBit::BlackBit);
}
MOZ_ALWAYS_INLINE MOZ_TSAN_BLACKLIST bool isMarkedGray(
const TenuredCell* cell) {
return !markBit(cell, ColorBit::BlackBit) &&
markBit(cell, ColorBit::GrayOrBlackBit);
}
MOZ_ALWAYS_INLINE bool markIfUnmarked(const TenuredCell* cell,
MarkColor color) {
uintptr_t *word, mask;
getMarkWordAndMask(cell, ColorBit::BlackBit, &word, &mask);
if (*word & mask) {
return false;
}
if (color == MarkColor::Black) {
*word |= mask;
} else {
getMarkWordAndMask(cell, ColorBit::GrayOrBlackBit, &word, &mask);
if (*word & mask) {
return false;
}
*word |= mask;
}
return true;
}
MOZ_ALWAYS_INLINE void markBlack(const TenuredCell* cell) {
uintptr_t *word, mask;
getMarkWordAndMask(cell, ColorBit::BlackBit, &word, &mask);
*word |= mask;
}
MOZ_ALWAYS_INLINE void copyMarkBit(TenuredCell* dst, const TenuredCell* src,
ColorBit colorBit) {
uintptr_t *srcWord, srcMask;
getMarkWordAndMask(src, colorBit, &srcWord, &srcMask);
uintptr_t *dstWord, dstMask;
getMarkWordAndMask(dst, colorBit, &dstWord, &dstMask);
*dstWord &= ~dstMask;
if (*srcWord & srcMask) {
*dstWord |= dstMask;
}
}
MOZ_ALWAYS_INLINE void unmark(const TenuredCell* cell) {
uintptr_t *word, mask;
getMarkWordAndMask(cell, ColorBit::BlackBit, &word, &mask);
*word &= ~mask;
getMarkWordAndMask(cell, ColorBit::GrayOrBlackBit, &word, &mask);
*word &= ~mask;
}
void clear() { memset((void*)bitmap, 0, sizeof(bitmap)); }
uintptr_t* arenaBits(Arena* arena) {
static_assert(
ArenaBitmapBits == ArenaBitmapWords * JS_BITS_PER_WORD,
"We assume that the part of the bitmap corresponding to the arena "
"has the exact number of words so we do not need to deal with a word "
"that covers bits from two arenas.");
uintptr_t *word, unused;
getMarkWordAndMask(reinterpret_cast<TenuredCell*>(arena->address()),
ColorBit::BlackBit, &word, &unused);
return word;
}
};
static_assert(ArenaBitmapBytes * ArenasPerChunk == sizeof(ChunkBitmap),
"Ensure our ChunkBitmap actually covers all arenas.");
static_assert(js::gc::ChunkMarkBitmapBits == ArenaBitmapBits * ArenasPerChunk,
"Ensure that the mark bitmap has the right number of bits.");
typedef BitArray<ArenasPerChunk> PerArenaBitmap;
const size_t ChunkPadSize = ChunkSize - (sizeof(Arena) * ArenasPerChunk) -
sizeof(ChunkBitmap) - sizeof(PerArenaBitmap) -
sizeof(ChunkInfo) - sizeof(ChunkTrailer);
static_assert(ChunkPadSize < BytesPerArenaWithHeader,
"If the chunk padding is larger than an arena, we should have "
"one more arena.");
struct Chunk {
Arena arenas[ArenasPerChunk];
uint8_t padding[ChunkPadSize];
ChunkBitmap bitmap;
PerArenaBitmap decommittedArenas;
ChunkInfo info;
ChunkTrailer trailer;
static Chunk* fromAddress(uintptr_t addr) {
addr &= ~ChunkMask;
return reinterpret_cast<Chunk*>(addr);
}
static bool withinValidRange(uintptr_t addr) {
uintptr_t offset = addr & ChunkMask;
return Chunk::fromAddress(addr)->isNurseryChunk()
? offset < ChunkSize - sizeof(ChunkTrailer)
: offset < ArenasPerChunk * ArenaSize;
}
static size_t arenaIndex(uintptr_t addr) {
MOZ_ASSERT(!Chunk::fromAddress(addr)->isNurseryChunk());
MOZ_ASSERT(withinValidRange(addr));
return (addr & ChunkMask) >> ArenaShift;
}
uintptr_t address() const {
uintptr_t addr = reinterpret_cast<uintptr_t>(this);
MOZ_ASSERT(!(addr & ChunkMask));
return addr;
}
bool unused() const { return info.numArenasFree == ArenasPerChunk; }
bool hasAvailableArenas() const { return info.numArenasFree != 0; }
bool isNurseryChunk() const { return trailer.storeBuffer; }
Arena* allocateArena(JSRuntime* rt, JS::Zone* zone, AllocKind kind,
const AutoLockGC& lock);
void releaseArena(JSRuntime* rt, Arena* arena, const AutoLockGC& lock);
void recycleArena(Arena* arena, SortedArenaList& dest, size_t thingsPerArena);
MOZ_MUST_USE bool decommitOneFreeArena(JSRuntime* rt, AutoLockGC& lock);
void decommitAllArenasWithoutUnlocking(const AutoLockGC& lock);
static Chunk* allocate(JSRuntime* rt);
void init(JSRuntime* rt);
private:
void decommitAllArenas();
unsigned findDecommittedArenaOffset();
Arena* fetchNextDecommittedArena();
void addArenaToFreeList(JSRuntime* rt, Arena* arena);
void addArenaToDecommittedList(const Arena* arena);
void updateChunkListAfterAlloc(JSRuntime* rt, const AutoLockGC& lock);
void updateChunkListAfterFree(JSRuntime* rt, const AutoLockGC& lock);
public:
Arena* fetchNextFreeArena(JSRuntime* rt);
};
static_assert(
sizeof(Chunk) == ChunkSize,
"Ensure the hardcoded chunk size definition actually matches the struct.");
static_assert(js::gc::ChunkMarkBitmapOffset == offsetof(Chunk, bitmap),
"The hardcoded API bitmap offset must match the actual offset.");
static_assert(js::gc::ChunkRuntimeOffset ==
offsetof(Chunk, trailer) + offsetof(ChunkTrailer, runtime),
"The hardcoded API runtime offset must match the actual offset.");
static_assert(
js::gc::ChunkLocationOffset ==
offsetof(Chunk, trailer) + offsetof(ChunkTrailer, location),
"The hardcoded API location offset must match the actual offset.");
static_assert(
js::gc::ChunkStoreBufferOffset ==
offsetof(Chunk, trailer) + offsetof(ChunkTrailer, storeBuffer),
"The hardcoded API storeBuffer offset must match the actual offset.");
class HeapSize {
HeapSize* const parent_;
mozilla::Atomic<size_t, mozilla::ReleaseAcquire,
mozilla::recordreplay::Behavior::DontPreserve>
gcBytes_;
public:
explicit HeapSize(HeapSize* parent) : parent_(parent), gcBytes_(0) {}
size_t gcBytes() const { return gcBytes_; }
void addGCArena() {
gcBytes_ += ArenaSize;
if (parent_) {
parent_->addGCArena();
}
}
void removeGCArena() {
MOZ_ASSERT(gcBytes_ >= ArenaSize);
gcBytes_ -= ArenaSize;
if (parent_) {
parent_->removeGCArena();
}
}
void adopt(HeapSize& other) {
gcBytes_ += other.gcBytes_;
other.gcBytes_ = 0;
}
};
inline void Arena::checkAddress() const {
mozilla::DebugOnly<uintptr_t> addr = uintptr_t(this);
MOZ_ASSERT(addr);
MOZ_ASSERT(!(addr & ArenaMask));
MOZ_ASSERT(Chunk::withinValidRange(addr));
}
inline Chunk* Arena::chunk() const { return Chunk::fromAddress(address()); }
inline bool InFreeList(Arena* arena, void* thing) {
uintptr_t addr = reinterpret_cast<uintptr_t>(thing);
MOZ_ASSERT(Arena::isAligned(addr, arena->getThingSize()));
return arena->inFreeList(addr);
}
static const int32_t ChunkLocationOffsetFromLastByte =
int32_t(gc::ChunkLocationOffset) - int32_t(gc::ChunkMask);
static const int32_t ChunkStoreBufferOffsetFromLastByte =
int32_t(gc::ChunkStoreBufferOffset) - int32_t(gc::ChunkMask);
}
namespace debug {
enum class MarkInfo : int {
BLACK = 0,
GRAY = 1,
UNMARKED = -1,
NURSERY = -2,
};
MOZ_NEVER_INLINE MarkInfo GetMarkInfo(js::gc::Cell* cell);
MOZ_NEVER_INLINE uintptr_t* GetMarkWordAddress(js::gc::Cell* cell);
MOZ_NEVER_INLINE uintptr_t GetMarkMask(js::gc::Cell* cell, uint32_t colorBit);
}
}
#endif