From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- js/src/gc/Zone.cpp | 979 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 979 insertions(+) create mode 100644 js/src/gc/Zone.cpp (limited to 'js/src/gc/Zone.cpp') diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp new file mode 100644 index 0000000000..70f260a5bc --- /dev/null +++ b/js/src/gc/Zone.cpp @@ -0,0 +1,979 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#include "gc/Zone-inl.h" +#include "js/shadow/Zone.h" // JS::shadow::Zone + +#include + +#include "gc/FreeOp.h" +#include "gc/GCLock.h" +#include "gc/Policy.h" +#include "gc/PublicIterators.h" +#include "jit/BaselineIC.h" +#include "jit/BaselineJIT.h" +#include "jit/Invalidation.h" +#include "jit/Ion.h" +#include "jit/JitZone.h" +#include "vm/Runtime.h" +#include "wasm/WasmInstance.h" + +#include "debugger/DebugAPI-inl.h" +#include "gc/GC-inl.h" +#include "gc/Marking-inl.h" +#include "gc/Nursery-inl.h" +#include "gc/WeakMap-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(1); + +ZoneAllocator::ZoneAllocator(JSRuntime* rt, Kind kind) + : JS::shadow::Zone(rt, &rt->gc.marker, kind), + gcHeapSize(&rt->gc.heapSize), + mallocHeapSize(nullptr), + jitHeapSize(nullptr), + jitHeapThreshold(jit::MaxCodeBytesPerProcess * 0.8) {} + +ZoneAllocator::~ZoneAllocator() { +#ifdef DEBUG + mallocTracker.checkEmptyOnDestroy(); + MOZ_ASSERT(gcHeapSize.bytes() == 0); + MOZ_ASSERT(mallocHeapSize.bytes() == 0); + MOZ_ASSERT(jitHeapSize.bytes() == 0); +#endif +} + +void ZoneAllocator::fixupAfterMovingGC() { +#ifdef DEBUG + mallocTracker.fixupAfterMovingGC(); +#endif +} + +void js::ZoneAllocator::updateMemoryCountersOnGCStart() { + gcHeapSize.updateOnGCStart(); + mallocHeapSize.updateOnGCStart(); +} + +void js::ZoneAllocator::updateGCStartThresholds( + GCRuntime& gc, JSGCInvocationKind invocationKind, + const js::AutoLockGC& lock) { + bool isAtomsZone = JS::Zone::from(this)->isAtomsZone(); + gcHeapThreshold.updateStartThreshold(gcHeapSize.retainedBytes(), + invocationKind, gc.tunables, + gc.schedulingState, isAtomsZone, lock); + mallocHeapThreshold.updateStartThreshold(mallocHeapSize.retainedBytes(), + gc.tunables, lock); +} + +void js::ZoneAllocator::setGCSliceThresholds(GCRuntime& gc) { + gcHeapThreshold.setSliceThreshold(this, gcHeapSize, gc.tunables); + mallocHeapThreshold.setSliceThreshold(this, mallocHeapSize, gc.tunables); + jitHeapThreshold.setSliceThreshold(this, jitHeapSize, gc.tunables); +} + +void js::ZoneAllocator::clearGCSliceThresholds() { + gcHeapThreshold.clearSliceThreshold(); + mallocHeapThreshold.clearSliceThreshold(); + jitHeapThreshold.clearSliceThreshold(); +} + +bool ZoneAllocator::addSharedMemory(void* mem, size_t nbytes, MemoryUse use) { + // nbytes can be zero here for SharedArrayBuffers. + + MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); + + auto ptr = sharedMemoryUseCounts.lookupForAdd(mem); + MOZ_ASSERT_IF(ptr, ptr->value().use == use); + + if (!ptr && !sharedMemoryUseCounts.add(ptr, mem, gc::SharedMemoryUse(use))) { + return false; + } + + ptr->value().count++; + + // Allocations can grow, so add any increase over the previous size and record + // the new size. + if (nbytes > ptr->value().nbytes) { + mallocHeapSize.addBytes(nbytes - ptr->value().nbytes); + ptr->value().nbytes = nbytes; + } + + maybeTriggerGCOnMalloc(); + + return true; +} + +void ZoneAllocator::removeSharedMemory(void* mem, size_t nbytes, + MemoryUse use) { + // nbytes can be zero here for SharedArrayBuffers. + + MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); + MOZ_ASSERT(CurrentThreadIsGCFinalizing()); + + auto ptr = sharedMemoryUseCounts.lookup(mem); + + MOZ_ASSERT(ptr); + MOZ_ASSERT(ptr->value().count != 0); + MOZ_ASSERT(ptr->value().use == use); + MOZ_ASSERT(ptr->value().nbytes >= nbytes); + + ptr->value().count--; + if (ptr->value().count == 0) { + mallocHeapSize.removeBytes(ptr->value().nbytes, true); + sharedMemoryUseCounts.remove(ptr); + } +} + +void ZoneAllocPolicy::decMemory(size_t nbytes) { + // Unfortunately we don't have enough context here to know whether we're being + // called on behalf of the collector so we have to do a TLS lookup to find + // out. + JSContext* cx = TlsContext.get(); + zone_->decNonGCMemory(this, nbytes, MemoryUse::ZoneAllocPolicy, + cx->defaultFreeOp()->isCollecting()); +} + +JS::Zone::Zone(JSRuntime* rt, Kind kind) + : ZoneAllocator(rt, kind), + // Note: don't use |this| before initializing helperThreadUse_! + // ProtectedData checks in CheckZone::check may read this field. + helperThreadUse_(HelperThreadUse::None), + helperThreadOwnerContext_(nullptr), + arenas(this), + data(this, nullptr), + tenuredBigInts(this, 0), + nurseryAllocatedStrings(this, 0), + markedStrings(this, 0), + finalizedStrings(this, 0), + allocNurseryStrings(this, true), + allocNurseryBigInts(this, true), + suppressAllocationMetadataBuilder(this, false), + previousGCStringStats(this), + stringStats(this), + uniqueIds_(this), + tenuredAllocsSinceMinorGC_(0), + gcWeakMapList_(this), + compartments_(), + crossZoneStringWrappers_(this), + gcGrayRoots_(this), + weakCaches_(this), + gcWeakKeys_(this, SystemAllocPolicy(), rt->randomHashCodeScrambler()), + gcNurseryWeakKeys_(this, SystemAllocPolicy(), + rt->randomHashCodeScrambler()), + typeDescrObjects_(this, this), + markedAtoms_(this), + atomCache_(this), + externalStringCache_(this), + functionToStringCache_(this), + propertyTree_(this, this), + baseShapes_(this, this), + initialShapes_(this, this), + nurseryShapes_(this), + finalizationRegistries_(this, this), + finalizationRecordMap_(this, this), + jitZone_(this, nullptr), + gcScheduled_(false), + gcScheduledSaved_(false), + gcPreserveCode_(false), + keepShapeCaches_(this, false), + wasCollected_(false), + listNext_(NotOnList), + weakRefMap_(this, this), + keptObjects(this, this) { + /* Ensure that there are no vtables to mess us up here. */ + MOZ_ASSERT(reinterpret_cast(this) == + static_cast(this)); + MOZ_ASSERT_IF(isAtomsZone(), !rt->unsafeAtomsZone()); + MOZ_ASSERT_IF(isSelfHostingZone(), !rt->hasInitializedSelfHosting()); + + // We can't call updateGCStartThresholds until the Zone has been constructed. + AutoLockGC lock(rt); + updateGCStartThresholds(rt->gc, GC_NORMAL, lock); +} + +Zone::~Zone() { + MOZ_ASSERT(helperThreadUse_ == HelperThreadUse::None); + MOZ_ASSERT(gcWeakMapList().isEmpty()); + MOZ_ASSERT_IF(regExps_.ref(), regExps().empty()); + + JSRuntime* rt = runtimeFromAnyThread(); + if (this == rt->gc.systemZone) { + MOZ_ASSERT(isSystemZone()); + rt->gc.systemZone = nullptr; + } + + js_delete(jitZone_.ref()); +} + +bool Zone::init() { + regExps_.ref() = make_unique(this); + return regExps_.ref() && gcWeakKeys().init() && gcNurseryWeakKeys().init(); +} + +void Zone::setNeedsIncrementalBarrier(bool needs) { + needsIncrementalBarrier_ = needs; +} + +void Zone::changeGCState(GCState prev, GCState next) { + MOZ_ASSERT(RuntimeHeapIsBusy()); + MOZ_ASSERT(canCollect()); + MOZ_ASSERT(gcState() == prev); + + // This can be called when barriers have been temporarily disabled by + // AutoDisableBarriers. In that case, don't update needsIncrementalBarrier_ + // and barriers will be re-enabled by ~AutoDisableBarriers() if necessary. + bool barriersDisabled = isGCMarking() && !needsIncrementalBarrier(); + + gcState_ = next; + + // Update the barriers state when we transition between marking and + // non-marking states, unless barriers have been disabled. + if (!barriersDisabled) { + needsIncrementalBarrier_ = isGCMarking(); + } +} + +template +static void EraseIf(js::gc::WeakEntryVector& entries, Pred pred) { + auto* begin = entries.begin(); + auto* const end = entries.end(); + + auto* newEnd = begin; + for (auto* p = begin; p != end; p++) { + if (!pred(*p)) { + *newEnd++ = *p; + } + } + + size_t removed = end - newEnd; + entries.shrinkBy(removed); +} + +static void SweepWeakEntryVectorWhileMinorSweeping( + js::gc::WeakEntryVector& entries) { + EraseIf(entries, [](js::gc::WeakMarkable& markable) -> bool { + return IsAboutToBeFinalizedDuringMinorSweep(&markable.key); + }); +} + +void Zone::sweepAfterMinorGC(JSTracer* trc) { + sweepWeakKeysAfterMinorGC(); + crossZoneStringWrappers().sweepAfterMinorGC(trc); +} + +void Zone::sweepWeakKeysAfterMinorGC() { + for (WeakKeyTable::Range r = gcNurseryWeakKeys().all(); !r.empty(); + r.popFront()) { + // Sweep gcNurseryWeakKeys to move live (forwarded) keys to gcWeakKeys, + // scanning through all the entries for such keys to update them. + // + // Forwarded and dead keys may also appear in their delegates' entries, + // so sweep those too (see below.) + + // The tricky case is when the key has a delegate that was already + // tenured. Then it will be in its compartment's gcWeakKeys, but we + // still need to update the key (which will be in the entries + // associated with it.) + gc::Cell* key = r.front().key; + MOZ_ASSERT(!key->isTenured()); + if (!Nursery::getForwardedPointer(&key)) { + // Dead nursery cell => discard. + continue; + } + + // Key been moved. The value is an array of pairs; update all + // keys in that array. + WeakEntryVector& entries = r.front().value; + SweepWeakEntryVectorWhileMinorSweeping(entries); + + // Live (moved) nursery cell. Append entries to gcWeakKeys. + auto entry = gcWeakKeys().get(key); + if (!entry) { + if (!gcWeakKeys().put(key, gc::WeakEntryVector())) { + AutoEnterOOMUnsafeRegion oomUnsafe; + oomUnsafe.crash("Failed to tenure weak keys entry"); + } + entry = gcWeakKeys().get(key); + } + + for (auto& markable : entries) { + if (!entry->value.append(markable)) { + AutoEnterOOMUnsafeRegion oomUnsafe; + oomUnsafe.crash("Failed to tenure weak keys entry"); + } + } + + // If the key has a delegate, then it will map to a WeakKeyEntryVector + // containing the key that needs to be updated. + + JSObject* delegate = gc::detail::GetDelegate(key->as()); + if (!delegate) { + continue; + } + MOZ_ASSERT(delegate->isTenured()); + + // If delegate was formerly nursery-allocated, we will sweep its + // entries when we visit its gcNurseryWeakKeys (if we haven't already). + // Note that we don't know the nursery address of the delegate, since + // the location it was stored in has already been updated. + // + // Otherwise, it will be in gcWeakKeys and we sweep it here. + auto p = delegate->zone()->gcWeakKeys().get(delegate); + if (p) { + SweepWeakEntryVectorWhileMinorSweeping(p->value); + } + } + + if (!gcNurseryWeakKeys().clear()) { + AutoEnterOOMUnsafeRegion oomUnsafe; + oomUnsafe.crash("OOM while clearing gcNurseryWeakKeys."); + } +} + +void Zone::sweepAllCrossCompartmentWrappers() { + crossZoneStringWrappers().sweep(); + for (CompartmentsInZoneIter comp(this); !comp.done(); comp.next()) { + comp->sweepCrossCompartmentObjectWrappers(); + } +} + +/* static */ +void Zone::fixupAllCrossCompartmentWrappersAfterMovingGC(JSTracer* trc) { + MOZ_ASSERT(trc->runtime()->gc.isHeapCompacting()); + + for (ZonesIter zone(trc->runtime(), WithAtoms); !zone.done(); zone.next()) { + // Sweep the wrapper map to update keys (wrapped values) in other + // compartments that may have been moved. + zone->crossZoneStringWrappers().sweep(); + + for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) { + comp->fixupCrossCompartmentObjectWrappersAfterMovingGC(trc); + } + } +} + +void Zone::dropStringWrappersOnGC() { + MOZ_ASSERT(JS::RuntimeHeapIsCollecting()); + crossZoneStringWrappers().clear(); +} + +#ifdef JSGC_HASH_TABLE_CHECKS + +void Zone::checkAllCrossCompartmentWrappersAfterMovingGC() { + checkStringWrappersAfterMovingGC(); + for (CompartmentsInZoneIter comp(this); !comp.done(); comp.next()) { + comp->checkObjectWrappersAfterMovingGC(); + } +} + +void Zone::checkStringWrappersAfterMovingGC() { + for (StringWrapperMap::Enum e(crossZoneStringWrappers()); !e.empty(); + e.popFront()) { + // Assert that the postbarriers have worked and that nothing is left in the + // wrapper map that points into the nursery, and that the hash table entries + // are discoverable. + auto key = e.front().key(); + CheckGCThingAfterMovingGC(key); + + auto ptr = crossZoneStringWrappers().lookup(key); + MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front()); + } +} +#endif + +void Zone::sweepWeakMaps() { + /* Finalize unreachable (key,value) pairs in all weak maps. */ + WeakMapBase::sweepZone(this); +} + +void Zone::discardJitCode(JSFreeOp* fop, + ShouldDiscardBaselineCode discardBaselineCode, + ShouldDiscardJitScripts discardJitScripts) { + if (!jitZone()) { + return; + } + + if (isPreservingCode()) { + return; + } + + if (discardBaselineCode || discardJitScripts) { +#ifdef DEBUG + // Assert no JitScripts are marked as active. + for (auto iter = cellIter(); !iter.done(); iter.next()) { + BaseScript* base = iter.unbarrieredGet(); + if (jit::JitScript* jitScript = base->maybeJitScript()) { + MOZ_ASSERT(!jitScript->active()); + } + } +#endif + + // Mark JitScripts on the stack as active. + jit::MarkActiveJitScripts(this); + } + + // Invalidate all Ion code in this zone. + jit::InvalidateAll(fop, this); + + for (auto base = cellIterUnsafe(); !base.done(); base.next()) { + jit::JitScript* jitScript = base->maybeJitScript(); + if (!jitScript) { + continue; + } + + JSScript* script = base->asJSScript(); + jit::FinishInvalidation(fop, script); + + // Discard baseline script if it's not marked as active. + if (discardBaselineCode) { + if (jitScript->hasBaselineScript() && !jitScript->active()) { + jit::FinishDiscardBaselineScript(fop, script); + } + } + +#ifdef JS_CACHEIR_SPEW + maybeUpdateWarmUpCount(script); +#endif + + // Warm-up counter for scripts are reset on GC. After discarding code we + // need to let it warm back up to get information such as which + // opcodes are setting array holes or accessing getter properties. + script->resetWarmUpCounterForGC(); + + // Try to release the script's JitScript. This should happen after + // releasing JIT code because we can't do this when the script still has + // JIT code. + if (discardJitScripts) { + script->maybeReleaseJitScript(fop); + jitScript = script->maybeJitScript(); + if (!jitScript) { + // Try to discard the ScriptCounts too. + if (!script->realm()->collectCoverageForDebug() && + !fop->runtime()->profilingScripts) { + script->destroyScriptCounts(); + } + continue; + } + } + + // If we did not release the JitScript, we need to purge optimized IC + // stubs because the optimizedStubSpace will be purged below. + if (discardBaselineCode) { + jitScript->purgeOptimizedStubs(script); + } + + // Finally, reset the active flag. + jitScript->resetActive(); + } + + /* + * When scripts contains pointers to nursery things, the store buffer + * can contain entries that point into the optimized stub space. Since + * this method can be called outside the context of a GC, this situation + * could result in us trying to mark invalid store buffer entries. + * + * Defer freeing any allocated blocks until after the next minor GC. + */ + if (discardBaselineCode) { + jitZone()->optimizedStubSpace()->freeAllAfterMinorGC(this); + jitZone()->purgeIonCacheIRStubInfo(); + } +} + +void JS::Zone::beforeClearDelegateInternal(JSObject* wrapper, + JSObject* delegate) { + MOZ_ASSERT(js::gc::detail::GetDelegate(wrapper) == delegate); + MOZ_ASSERT(needsIncrementalBarrier()); + GCMarker::fromTracer(barrierTracer())->severWeakDelegate(wrapper, delegate); +} + +void JS::Zone::afterAddDelegateInternal(JSObject* wrapper) { + JSObject* delegate = js::gc::detail::GetDelegate(wrapper); + if (delegate) { + GCMarker::fromTracer(barrierTracer()) + ->restoreWeakDelegate(wrapper, delegate); + } +} + +#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() { + // Zones in use by exclusive threads are not collected, and threads using + // them cannot access the main runtime's gcNumber without racing. + return usedByHelperThread() ? 0 : runtimeFromMainThread()->gc.gcNumber(); +} + +js::jit::JitZone* Zone::createJitZone(JSContext* cx) { + MOZ_ASSERT(!jitZone_); + MOZ_ASSERT(cx->runtime()->hasJitRuntime()); + + UniquePtr jitZone(cx->new_()); + 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() { + // The atoms zone cannot be collected while off-thread parsing is taking + // place. + if (isAtomsZone()) { + return !runtimeFromAnyThread()->hasHelperThreadZones(); + } + + // We don't collect the self hosting zone after it has been initialized. + if (isSelfHostingZone()) { + return !runtimeFromAnyThread()->gc.isSelfHostingZoneFrozen(); + } + + // Zones that will be or are currently used by other threads cannot be + // collected. + return !createdForHelperThread(); +} + +void Zone::notifyObservingDebuggers() { + AutoAssertNoGC nogc; + MOZ_ASSERT(JS::RuntimeHeapIsCollecting(), + "This method should be called during GC."); + + JSRuntime* rt = runtimeFromMainThread(); + + for (RealmsInZoneIter realms(this); !realms.done(); realms.next()) { + GlobalObject* global = realms->unsafeUnbarrieredMaybeGlobal(); + if (!global) { + continue; + } + + DebugAPI::notifyParticipatesInGC(global, rt->gc.majorGCCount()); + } +} + +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() { + ZoneAllocator::fixupAfterMovingGC(); + fixupInitialShapeTable(); +} + +bool Zone::addTypeDescrObject(JSContext* cx, HandleObject obj) { + // Type descriptor objects are always tenured so we don't need post barriers + // on the set. + 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); + arenas.checkEmptyArenaLists(); + + MOZ_ASSERT(compartments().length() == 1); + MOZ_ASSERT(compartments()[0] == comp); + MOZ_ASSERT(comp->realms().length() == 1); + + Realm* realm = comp->realms()[0]; + JSFreeOp* 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::purgeAtomCache() { + atomCache().clearAndCompact(); + + // Also purge the dtoa caches so that subsequent lookups populate atom + // cache too. + for (RealmsInZoneIter r(this); !r.done(); r.next()) { + r->dtoaCache.purge(); + } +} + +void Zone::addSizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf, JS::CodeSizes* code, size_t* regexpZone, + size_t* jitZone, size_t* baselineStubsOptimized, size_t* uniqueIdMap, + size_t* shapeCaches, size_t* atomsMarkBitmaps, size_t* compartmentObjects, + size_t* crossCompartmentWrappersTables, size_t* compartmentsPrivateData, + size_t* scriptCountsMapArg) { + *regexpZone += regExps().sizeOfExcludingThis(mallocSizeOf); + if (jitZone_) { + jitZone_->addSizeOfIncludingThis(mallocSizeOf, code, jitZone, + baselineStubsOptimized); + } + *uniqueIdMap += uniqueIds().shallowSizeOfExcludingThis(mallocSizeOf); + *shapeCaches += baseShapes().sizeOfExcludingThis(mallocSizeOf) + + initialShapes().sizeOfExcludingThis(mallocSizeOf); + *atomsMarkBitmaps += markedAtoms().sizeOfExcludingThis(mallocSizeOf); + *crossCompartmentWrappersTables += + crossZoneStringWrappers().sizeOfExcludingThis(mallocSizeOf); + + for (CompartmentsInZoneIter comp(this); !comp.done(); comp.next()) { + comp->addSizeOfIncludingThis(mallocSizeOf, compartmentObjects, + crossCompartmentWrappersTables, + compartmentsPrivateData); + } + + if (scriptCountsMap) { + *scriptCountsMapArg += + scriptCountsMap->shallowSizeOfIncludingThis(mallocSizeOf); + for (auto r = scriptCountsMap->all(); !r.empty(); r.popFront()) { + *scriptCountsMapArg += + r.front().value()->sizeOfIncludingThis(mallocSizeOf); + } + } +} + +void* ZoneAllocator::onOutOfMemory(js::AllocFunction allocFunc, + arena_id_t arena, size_t nbytes, + void* reallocPtr) { + if (!js::CurrentThreadCanAccessRuntime(runtime_)) { + return nullptr; + } + // The analysis sees that JSRuntime::onOutOfMemory could report an error, + // which with a JSErrorInterceptor could GC. But we're passing a null cx (to + // a default parameter) so the error will not be reported. + JS::AutoSuppressGCAnalysis suppress; + return runtimeFromMainThread()->onOutOfMemory(allocFunc, arena, nbytes, + reallocPtr); +} + +void ZoneAllocator::reportAllocationOverflow() const { + js::ReportAllocationOverflow(nullptr); +} + +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); +} + +void Zone::traceScriptTableRoots(JSTracer* trc) { + static_assert(std::is_convertible_v, + "BaseScript must not be nursery-allocated for script-table " + "tracing to work"); + + // Performance optimization: the script-table keys are JSScripts, which + // cannot be in the nursery, so we can skip this tracing if we are only in a + // minor collection. We static-assert this fact above. + if (JS::RuntimeHeapIsMinorCollecting()) { + return; + } + + // N.B.: the script-table keys are weak *except* in an exceptional case: when + // then --dump-bytecode command line option or the PCCount JSFriend API is + // used, then the scripts for all counts must remain alive. We only trace + // when the `trc->runtime()->profilingScripts` flag is set. This flag is + // cleared in JSRuntime::destroyRuntime() during shutdown to ensure that + // scripts are collected before the runtime goes away completely. + if (scriptCountsMap && trc->runtime()->profilingScripts) { + for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); + r.popFront()) { + BaseScript* script = const_cast(r.front().key()); + MOZ_ASSERT(script->hasScriptCounts()); + TraceRoot(trc, &script, "profilingScripts"); + MOZ_ASSERT(script == r.front().key(), "const_cast is only a work-around"); + } + } +} + +void Zone::fixupScriptMapsAfterMovingGC(JSTracer* trc) { + // Map entries are removed by BaseScript::finalize, but we need to update the + // script pointers here in case they are moved by the GC. + + if (scriptCountsMap) { + for (ScriptCountsMap::Enum e(*scriptCountsMap); !e.empty(); e.popFront()) { + BaseScript* script = e.front().key(); + TraceManuallyBarrieredEdge(trc, &script, "Realm::scriptCountsMap::key"); + if (script != e.front().key()) { + e.rekeyFront(script); + } + } + } + + if (scriptLCovMap) { + for (ScriptLCovMap::Enum e(*scriptLCovMap); !e.empty(); e.popFront()) { + BaseScript* script = e.front().key(); + if (!IsAboutToBeFinalizedUnbarriered(&script) && + script != e.front().key()) { + e.rekeyFront(script); + } + } + } + + if (debugScriptMap) { + for (DebugScriptMap::Enum e(*debugScriptMap); !e.empty(); e.popFront()) { + BaseScript* script = e.front().key(); + if (!IsAboutToBeFinalizedUnbarriered(&script) && + script != e.front().key()) { + e.rekeyFront(script); + } + } + } + +#ifdef MOZ_VTUNE + if (scriptVTuneIdMap) { + for (ScriptVTuneIdMap::Enum e(*scriptVTuneIdMap); !e.empty(); + e.popFront()) { + BaseScript* script = e.front().key(); + if (!IsAboutToBeFinalizedUnbarriered(&script) && + script != e.front().key()) { + e.rekeyFront(script); + } + } + } +#endif + +#ifdef JS_CACHEIR_SPEW + if (scriptFinalWarmUpCountMap) { + for (ScriptFinalWarmUpCountMap::Enum e(*scriptFinalWarmUpCountMap); + !e.empty(); e.popFront()) { + BaseScript* script = e.front().key(); + if (!IsAboutToBeFinalizedUnbarriered(&script) && + script != e.front().key()) { + e.rekeyFront(script); + } + } + } +#endif +} + +#ifdef JSGC_HASH_TABLE_CHECKS +void Zone::checkScriptMapsAfterMovingGC() { + if (scriptCountsMap) { + for (auto r = scriptCountsMap->all(); !r.empty(); r.popFront()) { + BaseScript* script = r.front().key(); + MOZ_ASSERT(script->zone() == this); + CheckGCThingAfterMovingGC(script); + auto ptr = scriptCountsMap->lookup(script); + MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front()); + } + } + + if (scriptLCovMap) { + for (auto r = scriptLCovMap->all(); !r.empty(); r.popFront()) { + BaseScript* script = r.front().key(); + MOZ_ASSERT(script->zone() == this); + CheckGCThingAfterMovingGC(script); + auto ptr = scriptLCovMap->lookup(script); + MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front()); + } + } + + if (debugScriptMap) { + for (auto r = debugScriptMap->all(); !r.empty(); r.popFront()) { + BaseScript* script = r.front().key(); + MOZ_ASSERT(script->zone() == this); + CheckGCThingAfterMovingGC(script); + DebugScript* ds = r.front().value().get(); + DebugAPI::checkDebugScriptAfterMovingGC(ds); + auto ptr = debugScriptMap->lookup(script); + MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front()); + } + } + +# ifdef MOZ_VTUNE + if (scriptVTuneIdMap) { + for (auto r = scriptVTuneIdMap->all(); !r.empty(); r.popFront()) { + BaseScript* script = r.front().key(); + MOZ_ASSERT(script->zone() == this); + CheckGCThingAfterMovingGC(script); + auto ptr = scriptVTuneIdMap->lookup(script); + MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front()); + } + } +# endif // MOZ_VTUNE + +# ifdef JS_CACHEIR_SPEW + if (scriptFinalWarmUpCountMap) { + for (auto r = scriptFinalWarmUpCountMap->all(); !r.empty(); r.popFront()) { + BaseScript* script = r.front().key(); + MOZ_ASSERT(script->zone() == this); + CheckGCThingAfterMovingGC(script); + auto ptr = scriptFinalWarmUpCountMap->lookup(script); + MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front()); + } + } +# endif // JS_CACHEIR_SPEW +} +#endif + +void Zone::clearScriptCounts(Realm* realm) { + if (!scriptCountsMap) { + return; + } + + // Clear all hasScriptCounts_ flags of BaseScript, in order to release all + // ScriptCounts entries of the given realm. + for (auto i = scriptCountsMap->modIter(); !i.done(); i.next()) { + BaseScript* script = i.get().key(); + if (script->realm() == realm) { + script->clearHasScriptCounts(); + i.remove(); + } + } +} + +void Zone::clearScriptLCov(Realm* realm) { + if (!scriptLCovMap) { + return; + } + + for (auto i = scriptLCovMap->modIter(); !i.done(); i.next()) { + BaseScript* script = i.get().key(); + if (script->realm() == realm) { + i.remove(); + } + } +} + +void Zone::clearRootsForShutdownGC() { + // Finalization callbacks are not called if we're shutting down. + finalizationRecordMap().clear(); + + clearKeptObjects(); +} + +void Zone::finishRoots() { + for (RealmsInZoneIter r(this); !r.done(); r.next()) { + r->finishRoots(); + } +} + +void Zone::traceKeptObjects(JSTracer* trc) { keptObjects.ref().trace(trc); } + +bool Zone::keepDuringJob(HandleObject target) { + return keptObjects.ref().put(target); +} + +void Zone::clearKeptObjects() { keptObjects.ref().clear(); } -- cgit v1.2.3