#include "gc/Zone-inl.h"
#include "gc/FreeOp.h"
#include "gc/Policy.h"
#include "gc/PublicIterators.h"
#include "jit/BaselineIC.h"
#include "jit/BaselineJIT.h"
#include "jit/Ion.h"
#include "jit/JitRealm.h"
#include "vm/Debugger.h"
#include "vm/Runtime.h"
#include "wasm/WasmInstance.h"
#include "gc/GC-inl.h"
#include "gc/Marking-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/Realm-inl.h"
using namespace js;
using namespace js::gc;
Zone* const Zone::NotOnList = reinterpret_cast<Zone*>(1);
JS::Zone::Zone(JSRuntime* rt)
: JS::shadow::Zone(rt, &rt->gc.marker),
helperThreadUse_(HelperThreadUse::None),
helperThreadOwnerContext_(nullptr),
debuggers(this, nullptr),
uniqueIds_(this),
suppressAllocationMetadataBuilder(this, false),
arenas(this),
tenuredAllocsSinceMinorGC_(0),
types(this),
gcWeakMapList_(this),
compartments_(),
gcGrayRoots_(this),
gcWeakRefs_(this),
weakCaches_(this),
gcWeakKeys_(this, SystemAllocPolicy(), rt->randomHashCodeScrambler()),
typeDescrObjects_(this, this),
markedAtoms_(this),
atomCache_(this),
externalStringCache_(this),
functionToStringCache_(this),
keepAtomsCount(this, 0),
purgeAtomsDeferred(this, 0),
zoneSize(&rt->gc.heapSize),
threshold(),
gcDelayBytes(0),
tenuredStrings(this, 0),
allocNurseryStrings(this, true),
propertyTree_(this, this),
baseShapes_(this, this),
initialShapes_(this, this),
nurseryShapes_(this),
data(this, nullptr),
isSystem(this, false),
#ifdef DEBUG
gcLastSweepGroupIndex(0),
#endif
jitZone_(this, nullptr),
gcScheduled_(false),
gcScheduledSaved_(false),
gcPreserveCode_(false),
keepShapeCaches_(this, false),
listNext_(NotOnList) {
MOZ_ASSERT(reinterpret_cast<JS::shadow::Zone*>(this) ==
static_cast<JS::shadow::Zone*>(this));
AutoLockGC lock(rt);
threshold.updateAfterGC(8192, GC_NORMAL, rt->gc.tunables,
rt->gc.schedulingState, lock);
setGCMaxMallocBytes(rt->gc.tunables.maxMallocBytes(), lock);
jitCodeCounter.setMax(jit::MaxCodeBytesPerProcess * 0.8, lock);
}
Zone::~Zone() {
MOZ_ASSERT(helperThreadUse_ == HelperThreadUse::None);
JSRuntime* rt = runtimeFromAnyThread();
if (this == rt->gc.systemZone) {
rt->gc.systemZone = nullptr;
}
js_delete(debuggers.ref());
js_delete(jitZone_.ref());
#ifdef DEBUG
if (!rt->gc.shutdownCollectedEverything()) {
gcWeakMapList().clear();
regExps().clear();
}
#endif
}
bool Zone::init(bool isSystemArg) {
isSystem = isSystemArg;
regExps_.ref() = make_unique<RegExpZone>(this);
return regExps_.ref() && gcWeakKeys().init();
}
void Zone::setNeedsIncrementalBarrier(bool needs) {
MOZ_ASSERT_IF(needs, canCollect());
needsIncrementalBarrier_ = needs;
}
void Zone::beginSweepTypes() { types.beginSweep(); }
Zone::DebuggerVector* Zone::getOrCreateDebuggers(JSContext* cx) {
if (debuggers) {
return debuggers;
}
debuggers = js_new<DebuggerVector>();
if (!debuggers) {
ReportOutOfMemory(cx);
}
return debuggers;
}
void Zone::sweepBreakpoints(FreeOp* fop) {
if (fop->runtime()->debuggerList().isEmpty()) {
return;
}
MOZ_ASSERT(isGCSweepingOrCompacting());
for (auto iter = cellIterUnsafe<JSScript>(); !iter.done(); iter.next()) {
JSScript* script = iter;
if (!script->hasAnyBreakpointsOrStepMode()) {
continue;
}
bool scriptGone = IsAboutToBeFinalizedUnbarriered(&script);
MOZ_ASSERT(script == iter);
for (unsigned i = 0; i < script->length(); i++) {
BreakpointSite* site = script->getBreakpointSite(script->offsetToPC(i));
if (!site) {
continue;
}
Breakpoint* nextbp;
for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) {
nextbp = bp->nextInSite();
GCPtrNativeObject& dbgobj = bp->debugger->toJSObjectRef();
MOZ_ASSERT_IF(isGCSweeping() && dbgobj->zone()->isCollecting(),
dbgobj->zone()->isGCSweeping() ||
(!scriptGone && dbgobj->asTenured().isMarkedAny()));
bool dying = scriptGone || IsAboutToBeFinalized(&dbgobj);
MOZ_ASSERT_IF(!dying, !IsAboutToBeFinalized(&bp->getHandlerRef()));
if (dying) {
bp->destroy(fop);
}
}
}
}
for (RealmsInZoneIter realms(this); !realms.done(); realms.next()) {
for (wasm::Instance* instance : realms->wasm.instances()) {
if (!instance->debugEnabled()) {
continue;
}
if (!IsAboutToBeFinalized(&instance->object_)) {
continue;
}
instance->debug().clearAllBreakpoints(fop, instance->objectUnbarriered());
}
}
}
void Zone::sweepWeakMaps() {
WeakMapBase::sweepZone(this);
}
void Zone::discardJitCode(FreeOp* fop,
ShouldDiscardBaselineCode discardBaselineCode,
ShouldReleaseTypes releaseTypes) {
if (!jitZone()) {
return;
}
if (isPreservingCode()) {
return;
}
if (discardBaselineCode || releaseTypes) {
#ifdef DEBUG
for (auto script = cellIter<JSScript>(); !script.done(); script.next()) {
if (TypeScript* types = script->types()) {
MOZ_ASSERT(!types->active());
}
}
#endif
jit::MarkActiveTypeScripts(this);
}
jit::InvalidateAll(fop, this);
for (auto script = cellIterUnsafe<JSScript>(); !script.done();
script.next()) {
jit::FinishInvalidation(fop, script);
if (discardBaselineCode && script->hasBaselineScript()) {
if (script->types()->active()) {
script->baselineScript()->clearIonCompiledOrInlined();
} else {
jit::FinishDiscardBaselineScript(fop, script);
}
}
script->resetWarmUpCounter();
if (script->hasBaselineScript()) {
script->baselineScript()->setControlFlowGraph(nullptr);
}
if (releaseTypes) {
script->maybeReleaseTypes();
}
if (discardBaselineCode && script->hasICScript()) {
script->icScript()->purgeOptimizedStubs(script);
}
if (TypeScript* types = script->types()) {
types->resetActive();
}
}
if (discardBaselineCode) {
jitZone()->optimizedStubSpace()->freeAllAfterMinorGC(this);
jitZone()->purgeIonCacheIRStubInfo();
}
jitZone()->cfgSpace()->lifoAlloc().freeAll();
}
#ifdef JSGC_HASH_TABLE_CHECKS
void JS::Zone::checkUniqueIdTableAfterMovingGC() {
for (auto r = uniqueIds().all(); !r.empty(); r.popFront()) {
js::gc::CheckGCThingAfterMovingGC(r.front().key());
}
}
#endif
uint64_t Zone::gcNumber() {
return usedByHelperThread() ? 0 : runtimeFromMainThread()->gc.gcNumber();
}
js::jit::JitZone* Zone::createJitZone(JSContext* cx) {
MOZ_ASSERT(!jitZone_);
MOZ_ASSERT(cx->runtime()->hasJitRuntime());
UniquePtr<jit::JitZone> jitZone(cx->new_<js::jit::JitZone>());
if (!jitZone) {
return nullptr;
}
jitZone_ = jitZone.release();
return jitZone_;
}
bool Zone::hasMarkedRealms() {
for (RealmsInZoneIter realm(this); !realm.done(); realm.next()) {
if (realm->marked()) {
return true;
}
}
return false;
}
bool Zone::canCollect() {
if (isAtomsZone()) {
return !runtimeFromAnyThread()->hasHelperThreadZones();
}
return !createdForHelperThread();
}
void Zone::notifyObservingDebuggers() {
MOZ_ASSERT(JS::RuntimeHeapIsCollecting(),
"This method should be called during GC.");
JSRuntime* rt = runtimeFromMainThread();
JSContext* cx = rt->mainContextFromOwnThread();
for (RealmsInZoneIter realms(this); !realms.done(); realms.next()) {
RootedGlobalObject global(cx, realms->unsafeUnbarrieredMaybeGlobal());
if (!global) {
continue;
}
GlobalObject::DebuggerVector* dbgs = global->getDebuggers();
if (!dbgs) {
continue;
}
for (GlobalObject::DebuggerVector::Range r = dbgs->all(); !r.empty();
r.popFront()) {
if (!r.front().unbarrieredGet()->debuggeeIsBeingCollected(
rt->gc.majorGCCount())) {
#ifdef DEBUG
fprintf(stderr,
"OOM while notifying observing Debuggers of a GC: The "
"onGarbageCollection\n"
"hook will not be fired for this GC for some Debuggers!\n");
#endif
return;
}
}
}
}
bool Zone::isOnList() const { return listNext_ != NotOnList; }
Zone* Zone::nextZone() const {
MOZ_ASSERT(isOnList());
return listNext_;
}
void Zone::clearTables() {
MOZ_ASSERT(regExps().empty());
baseShapes().clear();
initialShapes().clear();
}
void Zone::fixupAfterMovingGC() { fixupInitialShapeTable(); }
bool Zone::addTypeDescrObject(JSContext* cx, HandleObject obj) {
MOZ_ASSERT(!IsInsideNursery(obj));
if (!typeDescrObjects().put(obj)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
void Zone::deleteEmptyCompartment(JS::Compartment* comp) {
MOZ_ASSERT(comp->zone() == this);
MOZ_ASSERT(arenas.checkEmptyArenaLists());
MOZ_ASSERT(compartments().length() == 1);
MOZ_ASSERT(compartments()[0] == comp);
MOZ_ASSERT(comp->realms().length() == 1);
Realm* realm = comp->realms()[0];
FreeOp* fop = runtimeFromMainThread()->defaultFreeOp();
realm->destroy(fop);
comp->destroy(fop);
compartments().clear();
}
void Zone::setHelperThreadOwnerContext(JSContext* cx) {
MOZ_ASSERT_IF(cx, TlsContext.get() == cx);
helperThreadOwnerContext_ = cx;
}
bool Zone::ownedByCurrentHelperThread() {
MOZ_ASSERT(usedByHelperThread());
MOZ_ASSERT(TlsContext.get());
return helperThreadOwnerContext_ == TlsContext.get();
}
void Zone::releaseAtoms() {
MOZ_ASSERT(hasKeptAtoms());
keepAtomsCount--;
if (!hasKeptAtoms() && purgeAtomsDeferred) {
purgeAtomsDeferred = false;
purgeAtomCache();
}
}
void Zone::purgeAtomCacheOrDefer() {
if (hasKeptAtoms()) {
purgeAtomsDeferred = true;
return;
}
purgeAtomCache();
}
void Zone::purgeAtomCache() {
MOZ_ASSERT(!hasKeptAtoms());
MOZ_ASSERT(!purgeAtomsDeferred);
atomCache().clearAndCompact();
for (RealmsInZoneIter r(this); !r.done(); r.next()) {
r->dtoaCache.purge();
}
}
void Zone::traceAtomCache(JSTracer* trc) {
MOZ_ASSERT(hasKeptAtoms());
for (auto r = atomCache().all(); !r.empty(); r.popFront()) {
JSAtom* atom = r.front().asPtrUnbarriered();
TraceRoot(trc, &atom, "kept atom");
MOZ_ASSERT(r.front().asPtrUnbarriered() == atom);
}
}
void* Zone::onOutOfMemory(js::AllocFunction allocFunc, arena_id_t arena,
size_t nbytes, void* reallocPtr) {
if (!js::CurrentThreadCanAccessRuntime(runtime_)) {
return nullptr;
}
return runtimeFromMainThread()->onOutOfMemory(allocFunc, arena, nbytes,
reallocPtr);
}
void Zone::reportAllocationOverflow() { js::ReportAllocationOverflow(nullptr); }
void JS::Zone::maybeTriggerGCForTooMuchMalloc(js::gc::MemoryCounter& counter,
TriggerKind trigger) {
JSRuntime* rt = runtimeFromAnyThread();
if (!js::CurrentThreadCanAccessRuntime(rt)) {
return;
}
bool wouldInterruptGC = rt->gc.isIncrementalGCInProgress() && !isCollecting();
if (wouldInterruptGC && !counter.shouldResetIncrementalGC(rt->gc.tunables)) {
return;
}
if (!rt->gc.triggerZoneGC(this, JS::GCReason::TOO_MUCH_MALLOC,
counter.bytes(), counter.maxBytes())) {
return;
}
counter.recordTrigger(trigger);
}
ZoneList::ZoneList() : head(nullptr), tail(nullptr) {}
ZoneList::ZoneList(Zone* zone) : head(zone), tail(zone) {
MOZ_RELEASE_ASSERT(!zone->isOnList());
zone->listNext_ = nullptr;
}
ZoneList::~ZoneList() { MOZ_ASSERT(isEmpty()); }
void ZoneList::check() const {
#ifdef DEBUG
MOZ_ASSERT((head == nullptr) == (tail == nullptr));
if (!head) {
return;
}
Zone* zone = head;
for (;;) {
MOZ_ASSERT(zone && zone->isOnList());
if (zone == tail) break;
zone = zone->listNext_;
}
MOZ_ASSERT(!zone->listNext_);
#endif
}
bool ZoneList::isEmpty() const { return head == nullptr; }
Zone* ZoneList::front() const {
MOZ_ASSERT(!isEmpty());
MOZ_ASSERT(head->isOnList());
return head;
}
void ZoneList::append(Zone* zone) {
ZoneList singleZone(zone);
transferFrom(singleZone);
}
void ZoneList::transferFrom(ZoneList& other) {
check();
other.check();
if (!other.head) {
return;
}
MOZ_ASSERT(tail != other.tail);
if (tail) {
tail->listNext_ = other.head;
} else {
head = other.head;
}
tail = other.tail;
other.head = nullptr;
other.tail = nullptr;
}
Zone* ZoneList::removeFront() {
MOZ_ASSERT(!isEmpty());
check();
Zone* front = head;
head = head->listNext_;
if (!head) {
tail = nullptr;
}
front->listNext_ = Zone::NotOnList;
return front;
}
void ZoneList::clear() {
while (!isEmpty()) {
removeFront();
}
}
JS_PUBLIC_API void JS::shadow::RegisterWeakCache(
JS::Zone* zone, detail::WeakCacheBase* cachep) {
zone->registerWeakCache(cachep);
}