blob: 260f06dbe47497d51832fa4dd6287aa3e4a76495 [file] [log] [blame]
/* -*- 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(),
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();
}