mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-09-27 14:56:16 +00:00
450 lines
11 KiB
C++
450 lines
11 KiB
C++
|
/* -*- 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/. */
|
||
|
|
||
|
#include "gc/Zone.h"
|
||
|
|
||
|
#include "jsgc.h"
|
||
|
|
||
|
#include "jit/BaselineJIT.h"
|
||
|
#include "jit/Ion.h"
|
||
|
#include "jit/JitCompartment.h"
|
||
|
#include "vm/Debugger.h"
|
||
|
#include "vm/Runtime.h"
|
||
|
|
||
|
#include "jscompartmentinlines.h"
|
||
|
#include "jsgcinlines.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),
|
||
|
debuggers(nullptr),
|
||
|
arenas(rt),
|
||
|
types(this),
|
||
|
compartments(),
|
||
|
gcGrayRoots(),
|
||
|
gcWeakKeys(SystemAllocPolicy(), rt->randomHashCodeScrambler()),
|
||
|
hasDeadProxies(false),
|
||
|
gcMallocBytes(0),
|
||
|
gcMallocGCTriggered(false),
|
||
|
usage(&rt->gc.usage),
|
||
|
gcDelayBytes(0),
|
||
|
data(nullptr),
|
||
|
isSystem(false),
|
||
|
usedByExclusiveThread(false),
|
||
|
active(false),
|
||
|
jitZone_(nullptr),
|
||
|
gcState_(NoGC),
|
||
|
gcScheduled_(false),
|
||
|
gcPreserveCode_(false),
|
||
|
jitUsingBarriers_(false),
|
||
|
listNext_(NotOnList)
|
||
|
{
|
||
|
/* Ensure that there are no vtables to mess us up here. */
|
||
|
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.maxMallocBytesAllocated() * 0.9);
|
||
|
}
|
||
|
|
||
|
Zone::~Zone()
|
||
|
{
|
||
|
JSRuntime* rt = runtimeFromMainThread();
|
||
|
if (this == rt->gc.systemZone)
|
||
|
rt->gc.systemZone = nullptr;
|
||
|
|
||
|
js_delete(debuggers);
|
||
|
js_delete(jitZone_);
|
||
|
}
|
||
|
|
||
|
bool Zone::init(bool isSystemArg)
|
||
|
{
|
||
|
isSystem = isSystemArg;
|
||
|
return uniqueIds_.init() && gcZoneGroupEdges.init() && gcWeakKeys.init();
|
||
|
}
|
||
|
|
||
|
void
|
||
|
Zone::setNeedsIncrementalBarrier(bool needs, ShouldUpdateJit updateJit)
|
||
|
{
|
||
|
if (updateJit == UpdateJit && needs != jitUsingBarriers_) {
|
||
|
jit::ToggleBarriers(this, needs);
|
||
|
jitUsingBarriers_ = needs;
|
||
|
}
|
||
|
|
||
|
MOZ_ASSERT_IF(needs && isAtomsZone(), !runtimeFromMainThread()->exclusiveThreadsPresent());
|
||
|
MOZ_ASSERT_IF(needs, canCollect());
|
||
|
needsIncrementalBarrier_ = needs;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
Zone::resetGCMallocBytes()
|
||
|
{
|
||
|
gcMallocBytes = ptrdiff_t(gcMaxMallocBytes);
|
||
|
gcMallocGCTriggered = false;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
Zone::setGCMaxMallocBytes(size_t value)
|
||
|
{
|
||
|
/*
|
||
|
* For compatibility treat any value that exceeds PTRDIFF_T_MAX to
|
||
|
* mean that value.
|
||
|
*/
|
||
|
gcMaxMallocBytes = (ptrdiff_t(value) >= 0) ? value : size_t(-1) >> 1;
|
||
|
resetGCMallocBytes();
|
||
|
}
|
||
|
|
||
|
void
|
||
|
Zone::onTooMuchMalloc()
|
||
|
{
|
||
|
if (!gcMallocGCTriggered) {
|
||
|
GCRuntime& gc = runtimeFromAnyThread()->gc;
|
||
|
gcMallocGCTriggered = gc.triggerZoneGC(this, JS::gcreason::TOO_MUCH_MALLOC);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
Zone::beginSweepTypes(FreeOp* fop, bool releaseTypes)
|
||
|
{
|
||
|
// Periodically release observed types for all scripts. This is safe to
|
||
|
// do when there are no frames for the zone on the stack.
|
||
|
if (active)
|
||
|
releaseTypes = false;
|
||
|
|
||
|
AutoClearTypeInferenceStateOnOOM oom(this);
|
||
|
types.beginSweep(fop, releaseTypes, oom);
|
||
|
}
|
||
|
|
||
|
Zone::DebuggerVector*
|
||
|
Zone::getOrCreateDebuggers(JSContext* cx)
|
||
|
{
|
||
|
if (debuggers)
|
||
|
return debuggers;
|
||
|
|
||
|
debuggers = js_new<DebuggerVector>();
|
||
|
if (!debuggers)
|
||
|
ReportOutOfMemory(cx);
|
||
|
return debuggers;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
Zone::logPromotionsToTenured()
|
||
|
{
|
||
|
auto* dbgs = getDebuggers();
|
||
|
if (MOZ_LIKELY(!dbgs))
|
||
|
return;
|
||
|
|
||
|
auto now = JS_GetCurrentEmbedderTime();
|
||
|
JSRuntime* rt = runtimeFromAnyThread();
|
||
|
|
||
|
for (auto** dbgp = dbgs->begin(); dbgp != dbgs->end(); dbgp++) {
|
||
|
if (!(*dbgp)->isEnabled() || !(*dbgp)->isTrackingTenurePromotions())
|
||
|
continue;
|
||
|
|
||
|
for (auto range = awaitingTenureLogging.all(); !range.empty(); range.popFront()) {
|
||
|
if ((*dbgp)->isDebuggeeUnbarriered(range.front()->compartment()))
|
||
|
(*dbgp)->logTenurePromotion(rt, *range.front(), now);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
awaitingTenureLogging.clear();
|
||
|
}
|
||
|
|
||
|
void
|
||
|
Zone::sweepBreakpoints(FreeOp* fop)
|
||
|
{
|
||
|
if (fop->runtime()->debuggerList.isEmpty())
|
||
|
return;
|
||
|
|
||
|
/*
|
||
|
* Sweep all compartments in a zone at the same time, since there is no way
|
||
|
* to iterate over the scripts belonging to a single compartment in a zone.
|
||
|
*/
|
||
|
|
||
|
MOZ_ASSERT(isGCSweepingOrCompacting());
|
||
|
for (ZoneCellIterUnderGC i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
|
||
|
JSScript* script = i.get<JSScript>();
|
||
|
if (!script->hasAnyBreakpointsOrStepMode())
|
||
|
continue;
|
||
|
|
||
|
bool scriptGone = IsAboutToBeFinalizedUnbarriered(&script);
|
||
|
MOZ_ASSERT(script == i.get<JSScript>());
|
||
|
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();
|
||
|
HeapPtrNativeObject& dbgobj = bp->debugger->toJSObjectRef();
|
||
|
|
||
|
// If we are sweeping, then we expect the script and the
|
||
|
// debugger object to be swept in the same zone group, except if
|
||
|
// the breakpoint was added after we computed the zone
|
||
|
// groups. In this case both script and debugger object must be
|
||
|
// live.
|
||
|
MOZ_ASSERT_IF(isGCSweeping() && dbgobj->zone()->isCollecting(),
|
||
|
dbgobj->zone()->isGCSweeping() ||
|
||
|
(!scriptGone && dbgobj->asTenured().isMarked()));
|
||
|
|
||
|
bool dying = scriptGone || IsAboutToBeFinalized(&dbgobj);
|
||
|
MOZ_ASSERT_IF(!dying, !IsAboutToBeFinalized(&bp->getHandlerRef()));
|
||
|
if (dying)
|
||
|
bp->destroy(fop);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
Zone::sweepWeakMaps()
|
||
|
{
|
||
|
/* Finalize unreachable (key,value) pairs in all weak maps. */
|
||
|
WeakMapBase::sweepZone(this);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
Zone::discardJitCode(FreeOp* fop)
|
||
|
{
|
||
|
if (!jitZone())
|
||
|
return;
|
||
|
|
||
|
if (isPreservingCode()) {
|
||
|
PurgeJITCaches(this);
|
||
|
} else {
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
/* Assert no baseline scripts are marked as active. */
|
||
|
for (ZoneCellIterUnderGC i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
|
||
|
JSScript* script = i.get<JSScript>();
|
||
|
MOZ_ASSERT_IF(script->hasBaselineScript(), !script->baselineScript()->active());
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Mark baseline scripts on the stack as active. */
|
||
|
jit::MarkActiveBaselineScripts(this);
|
||
|
|
||
|
/* Only mark OSI points if code is being discarded. */
|
||
|
jit::InvalidateAll(fop, this);
|
||
|
|
||
|
for (ZoneCellIterUnderGC i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
|
||
|
JSScript* script = i.get<JSScript>();
|
||
|
jit::FinishInvalidation(fop, script);
|
||
|
|
||
|
/*
|
||
|
* Discard baseline script if it's not marked as active. Note that
|
||
|
* this also resets the active flag.
|
||
|
*/
|
||
|
jit::FinishDiscardBaselineScript(fop, script);
|
||
|
|
||
|
/*
|
||
|
* 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->resetWarmUpCounter();
|
||
|
}
|
||
|
|
||
|
jitZone()->optimizedStubSpace()->free();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef JSGC_HASH_TABLE_CHECKS
|
||
|
void
|
||
|
JS::Zone::checkUniqueIdTableAfterMovingGC()
|
||
|
{
|
||
|
for (UniqueIdMap::Enum e(uniqueIds_); !e.empty(); e.popFront())
|
||
|
js::gc::CheckGCThingAfterMovingGC(e.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 usedByExclusiveThread ? 0 : runtimeFromMainThread()->gc.gcNumber();
|
||
|
}
|
||
|
|
||
|
js::jit::JitZone*
|
||
|
Zone::createJitZone(JSContext* cx)
|
||
|
{
|
||
|
MOZ_ASSERT(!jitZone_);
|
||
|
|
||
|
if (!cx->runtime()->getJitRuntime(cx))
|
||
|
return nullptr;
|
||
|
|
||
|
jitZone_ = cx->new_<js::jit::JitZone>();
|
||
|
return jitZone_;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
Zone::hasMarkedCompartments()
|
||
|
{
|
||
|
for (CompartmentsInZoneIter comp(this); !comp.done(); comp.next()) {
|
||
|
if (comp->marked)
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
Zone::canCollect()
|
||
|
{
|
||
|
// Zones cannot be collected while in use by other threads.
|
||
|
if (usedByExclusiveThread)
|
||
|
return false;
|
||
|
JSRuntime* rt = runtimeFromAnyThread();
|
||
|
if (isAtomsZone() && rt->exclusiveThreadsPresent())
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
Zone::notifyObservingDebuggers()
|
||
|
{
|
||
|
for (CompartmentsInZoneIter comps(this); !comps.done(); comps.next()) {
|
||
|
JSRuntime* rt = runtimeFromAnyThread();
|
||
|
RootedGlobalObject global(rt, comps->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()->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
|
||
|
js::ZonesIter::atAtomsZone(JSRuntime* rt)
|
||
|
{
|
||
|
return rt->isAtomsZone(*it);
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
Zone::isOnList() const
|
||
|
{
|
||
|
return listNext_ != NotOnList;
|
||
|
}
|
||
|
|
||
|
Zone*
|
||
|
Zone::nextZone() const
|
||
|
{
|
||
|
MOZ_ASSERT(isOnList());
|
||
|
return listNext_;
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
MOZ_ASSERT(tail != other.tail);
|
||
|
|
||
|
if (tail)
|
||
|
tail->listNext_ = other.head;
|
||
|
else
|
||
|
head = other.head;
|
||
|
tail = other.tail;
|
||
|
|
||
|
other.head = nullptr;
|
||
|
other.tail = nullptr;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ZoneList::removeFront()
|
||
|
{
|
||
|
MOZ_ASSERT(!isEmpty());
|
||
|
check();
|
||
|
|
||
|
Zone* front = head;
|
||
|
head = head->listNext_;
|
||
|
if (!head)
|
||
|
tail = nullptr;
|
||
|
|
||
|
front->listNext_ = Zone::NotOnList;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ZoneList::clear()
|
||
|
{
|
||
|
while (!isEmpty())
|
||
|
removeFront();
|
||
|
}
|