diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 2413a06591ff..1a623f839453 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -328,7 +328,7 @@ Statistics::formatData(StatisticsSerializer &ss, uint64_t timestamp) if (ss.isJSON()) ss.appendNumber("Timestamp", "%llu", "", (unsigned long long)timestamp); ss.appendNumber("Total Time", "%.1f", "ms", t(total)); - ss.appendString("Type", compartment ? "compartment" : "global"); + ss.appendString("Type", wasFullGC ? "global" : "compartment"); ss.appendNumber("MMU (20ms)", "%d", "%", int(mmu20 * 100)); ss.appendNumber("MMU (50ms)", "%d", "%", int(mmu50 * 100)); if (slices.length() > 1 || ss.isJSON()) @@ -398,7 +398,7 @@ Statistics::Statistics(JSRuntime *rt) startupTime(PRMJ_Now()), fp(NULL), fullFormat(false), - compartment(NULL), + wasFullGC(false), nonincrementalReason(NULL) { PodArrayZero(phaseTotals); @@ -492,7 +492,7 @@ Statistics::endGC() phaseTotals[i] += phaseTimes[i]; if (JSAccumulateTelemetryDataCallback cb = runtime->telemetryCallback) { - (*cb)(JS_TELEMETRY_GC_IS_COMPARTMENTAL, compartment ? 1 : 0); + (*cb)(JS_TELEMETRY_GC_IS_COMPARTMENTAL, wasFullGC ? 0 : 1); (*cb)(JS_TELEMETRY_GC_MS, t(gcDuration())); (*cb)(JS_TELEMETRY_GC_MARK_MS, t(phaseTimes[PHASE_MARK])); (*cb)(JS_TELEMETRY_GC_SWEEP_MS, t(phaseTimes[PHASE_SWEEP])); @@ -508,9 +508,9 @@ Statistics::endGC() } void -Statistics::beginSlice(JSCompartment *comp, gcreason::Reason reason) +Statistics::beginSlice(bool full, gcreason::Reason reason) { - compartment = comp; + wasFullGC = full; bool first = runtime->gcIncrementalState == gc::NO_INCREMENTAL; if (first) @@ -523,7 +523,7 @@ Statistics::beginSlice(JSCompartment *comp, gcreason::Reason reason) (*cb)(JS_TELEMETRY_GC_REASON, reason); if (GCSliceCallback cb = runtime->gcSliceCallback) - (*cb)(runtime, first ? GC_CYCLE_BEGIN : GC_SLICE_BEGIN, GCDescription(!!compartment)); + (*cb)(runtime, first ? GC_CYCLE_BEGIN : GC_SLICE_BEGIN, GCDescription(!wasFullGC)); } void @@ -542,9 +542,9 @@ Statistics::endSlice() if (GCSliceCallback cb = runtime->gcSliceCallback) { if (last) - (*cb)(runtime, GC_CYCLE_END, GCDescription(!!compartment)); + (*cb)(runtime, GC_CYCLE_END, GCDescription(!wasFullGC)); else - (*cb)(runtime, GC_SLICE_END, GCDescription(!!compartment)); + (*cb)(runtime, GC_SLICE_END, GCDescription(!wasFullGC)); } /* Do this after the slice callback since it uses these values. */ diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 2c1df39ff22f..963e36ffa276 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -94,7 +94,7 @@ struct Statistics { void beginPhase(Phase phase); void endPhase(Phase phase); - void beginSlice(JSCompartment *comp, gcreason::Reason reason); + void beginSlice(bool full, gcreason::Reason reason); void endSlice(); void reset(const char *reason) { slices.back().resetReason = reason; } @@ -116,7 +116,7 @@ struct Statistics { FILE *fp; bool fullFormat; - JSCompartment *compartment; + bool wasFullGC; const char *nonincrementalReason; struct SliceData { @@ -162,9 +162,9 @@ struct Statistics { }; struct AutoGCSlice { - AutoGCSlice(Statistics &stats, JSCompartment *comp, gcreason::Reason reason + AutoGCSlice(Statistics &stats, bool full, gcreason::Reason reason JS_GUARD_OBJECT_NOTIFIER_PARAM) - : stats(stats) { JS_GUARD_OBJECT_NOTIFIER_INIT; stats.beginSlice(comp, reason); } + : stats(stats) { JS_GUARD_OBJECT_NOTIFIER_INIT; stats.beginSlice(full, reason); } ~AutoGCSlice() { stats.endSlice(); } Statistics &stats; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 8b5c5c981c86..647ce24ec7ca 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -730,21 +730,21 @@ JSRuntime::JSRuntime() gcJitReleaseTime(0), gcMode(JSGC_MODE_GLOBAL), gcIsNeeded(0), + gcFullIsNeeded(0), gcWeakMapList(NULL), gcStats(thisFromCtor()), gcNumber(0), gcStartNumber(0), gcTriggerReason(gcreason::NO_REASON), - gcTriggerCompartment(NULL), - gcCurrentCompartment(NULL), - gcCheckCompartment(NULL), + gcIsFull(false), + gcStrictCompartmentChecking(false), gcIncrementalState(gc::NO_INCREMENTAL), gcCompartmentCreated(false), gcLastMarkSlice(false), + gcIncrementalIsFull(false), gcInterFrameGC(0), gcSliceBudget(SliceBudget::Unlimited), gcIncrementalEnabled(true), - gcIncrementalCompartment(NULL), gcPoke(false), gcRunning(false), #ifdef JS_GC_ZEAL @@ -2829,7 +2829,7 @@ JS_IsGCMarkingTracer(JSTracer *trc) return IS_GC_MARKING_TRACER(trc); } -JS_PUBLIC_API(void) +extern JS_PUBLIC_API(void) JS_CompartmentGC(JSContext *cx, JSCompartment *comp) { AssertNoGC(cx); @@ -2837,7 +2837,12 @@ JS_CompartmentGC(JSContext *cx, JSCompartment *comp) /* We cannot GC the atoms compartment alone; use a full GC instead. */ JS_ASSERT(comp != cx->runtime->atomsCompartment); - GC(cx, comp, GC_NORMAL, gcreason::API); + if (comp) { + PrepareCompartmentForGC(comp); + GC(cx, false, GC_NORMAL, gcreason::API); + } else { + GC(cx, true, GC_NORMAL, gcreason::API); + } } JS_PUBLIC_API(void) diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 760f0cf7791a..c390789bb2e1 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -261,7 +261,7 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) rt->gcHelperThread.waitBackgroundSweepEnd(); } #endif - + /* * Dump remaining type inference results first. This printing * depends on atoms still existing. @@ -271,16 +271,16 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) /* Unpin all common atoms before final GC. */ js_FinishCommonAtoms(cx); - + /* Clear debugging state to remove GC roots. */ for (CompartmentsIter c(rt); !c.done(); c.next()) c->clearTraps(cx); JS_ClearAllWatchPoints(cx); - - GC(cx, NULL, GC_NORMAL, gcreason::LAST_CONTEXT); + + GC(cx, true, GC_NORMAL, gcreason::LAST_CONTEXT); } else if (mode == JSDCM_FORCE_GC) { JS_ASSERT(!rt->gcRunning); - GC(cx, NULL, GC_NORMAL, gcreason::DESTROY_CONTEXT); + GC(cx, true, GC_NORMAL, gcreason::DESTROY_CONTEXT); } else if (mode == JSDCM_MAYBE_GC) { JS_ASSERT(!rt->gcRunning); JS_MaybeGC(cx); @@ -883,7 +883,7 @@ js_InvokeOperationCallback(JSContext *cx) JS_ATOMIC_SET(&rt->interrupt, 0); if (rt->gcIsNeeded) - GCSlice(cx, rt->gcTriggerCompartment, GC_NORMAL, rt->gcTriggerReason); + GCSlice(cx, rt->gcFullIsNeeded, GC_NORMAL, rt->gcTriggerReason); #ifdef JS_THREADSAFE /* diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 38531aa08382..c329e4b0a8ca 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -306,7 +306,15 @@ struct JSRuntime : js::RuntimeFriendFields int64_t gcNextFullGCTime; int64_t gcJitReleaseTime; JSGCMode gcMode; + + /* + * These flags must be kept separate so that a thread requesting a + * compartment GC doesn't cancel another thread's concurrent request for a + * full GC. + */ volatile uintptr_t gcIsNeeded; + volatile uintptr_t gcFullIsNeeded; + js::WeakMapBase *gcWeakMapList; js::gcstats::Statistics gcStats; @@ -319,20 +327,14 @@ struct JSRuntime : js::RuntimeFriendFields /* The reason that an interrupt-triggered GC should be called. */ js::gcreason::Reason gcTriggerReason; - /* - * Compartment that triggered GC. If more than one Compatment need GC, - * gcTriggerCompartment is reset to NULL and a global GC is performed. - */ - JSCompartment *gcTriggerCompartment; - - /* Compartment that is currently involved in per-compartment GC */ - JSCompartment *gcCurrentCompartment; + /* Is the currently running GC a full GC or a compartmental GC? */ + bool gcIsFull; /* - * If this is non-NULL, all marked objects must belong to this compartment. - * This is used to look for compartment bugs. + * If this is true, all marked objects must belong to a compartment being + * GCed. This is used to look for compartment bugs. */ - JSCompartment *gcCheckCompartment; + bool gcStrictCompartmentChecking; /* * The current incremental GC phase. During non-incremental GC, this is @@ -346,6 +348,9 @@ struct JSRuntime : js::RuntimeFriendFields /* Indicates that the last incremental slice exhausted the mark stack. */ bool gcLastMarkSlice; + /* Is there a full incremental GC in progress. */ + bool gcIncrementalIsFull; + /* * Indicates that a GC slice has taken place in the middle of an animation * frame, rather than at the beginning. In this case, the next slice will be @@ -362,9 +367,6 @@ struct JSRuntime : js::RuntimeFriendFields */ bool gcIncrementalEnabled; - /* Compartment that is undergoing an incremental GC. */ - JSCompartment *gcIncrementalCompartment; - /* * We save all conservative scanned roots in this vector so that * conservative scanning can be "replayed" deterministically. In DEBUG mode, diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 95a258da79c9..4baa9ead1e73 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -73,6 +73,7 @@ JSCompartment::JSCompartment(JSRuntime *rt) : rt(rt), principals(NULL), needsBarrier_(false), + gcState(NoGCScheduled), gcBytes(0), gcTriggerBytes(0), hold(false), @@ -420,7 +421,7 @@ JSCompartment::wrap(JSContext *cx, AutoIdVector &props) void JSCompartment::markCrossCompartmentWrappers(JSTracer *trc) { - JS_ASSERT(trc->runtime->gcCurrentCompartment); + JS_ASSERT(!isCollecting()); for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { Value tmp = e.front().key; diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 466742453f94..2e9de2f3c575 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -194,7 +194,7 @@ struct JSCompartment bool needsBarrier_; - bool needsBarrier() { + bool needsBarrier() const { return needsBarrier_; } @@ -203,6 +203,52 @@ struct JSCompartment return &rt->gcMarker; } + private: + enum CompartmentGCState { + NoGCScheduled, + GCScheduled, + GCRunning + }; + + CompartmentGCState gcState; + + public: + bool isCollecting() const { + /* Allow this if we're in the middle of an incremental GC. */ + if (rt->gcRunning) { + return gcState == GCRunning; + } else { + JS_ASSERT(gcState != GCRunning); + return needsBarrier(); + } + } + + /* + * If this returns true, all object tracing must be done with a GC marking + * tracer. + */ + bool requireGCTracer() const { + return gcState == GCRunning; + } + + void setCollecting(bool collecting) { + JS_ASSERT(rt->gcRunning); + if (collecting) + gcState = GCRunning; + else + gcState = NoGCScheduled; + } + + void scheduleGC() { + JS_ASSERT(!rt->gcRunning); + JS_ASSERT(gcState != GCRunning); + gcState = GCScheduled; + } + + bool isGCScheduled() const { + return gcState == GCScheduled; + } + size_t gcBytes; size_t gcTriggerBytes; size_t gcMaxMallocBytes; diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index 3797a6cb2d6a..caf302c6f594 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -134,7 +134,7 @@ JS_NewObjectWithUniqueType(JSContext *cx, JSClass *clasp, JSObject *proto, JSObj JS_FRIEND_API(void) js::GCForReason(JSContext *cx, gcreason::Reason reason) { - GC(cx, NULL, GC_NORMAL, reason); + GC(cx, true, GC_NORMAL, reason); } JS_FRIEND_API(void) @@ -143,19 +143,20 @@ js::CompartmentGCForReason(JSContext *cx, JSCompartment *comp, gcreason::Reason /* We cannot GC the atoms compartment alone; use a full GC instead. */ JS_ASSERT(comp != cx->runtime->atomsCompartment); - GC(cx, comp, GC_NORMAL, reason); + PrepareCompartmentForGC(comp); + GC(cx, false, GC_NORMAL, reason); } JS_FRIEND_API(void) js::ShrinkingGC(JSContext *cx, gcreason::Reason reason) { - GC(cx, NULL, GC_SHRINK, reason); + GC(cx, true, GC_SHRINK, reason); } JS_FRIEND_API(void) js::IncrementalGC(JSContext *cx, gcreason::Reason reason) { - GCSlice(cx, NULL, GC_NORMAL, reason); + GCSlice(cx, true, GC_NORMAL, reason); } JS_FRIEND_API(void) @@ -752,12 +753,17 @@ NotifyDidPaint(JSContext *cx) } if (rt->gcZeal() == gc::ZealFrameGCValue) { - GCSlice(cx, NULL, GC_NORMAL, gcreason::REFRESH_FRAME); + GCSlice(cx, true, GC_NORMAL, gcreason::REFRESH_FRAME); return; } - if (rt->gcIncrementalState != gc::NO_INCREMENTAL && !rt->gcInterFrameGC) - GCSlice(cx, rt->gcIncrementalCompartment, GC_NORMAL, gcreason::REFRESH_FRAME); + if (rt->gcIncrementalState != gc::NO_INCREMENTAL && !rt->gcInterFrameGC) { + for (CompartmentsIter c(rt); !c.done(); c.next()) { + if (c->needsBarrier()) + PrepareCompartmentForGC(c); + } + GCSlice(cx, rt->gcIncrementalIsFull, GC_NORMAL, gcreason::REFRESH_FRAME); + } rt->gcInterFrameGC = false; } diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 0b7e2aa813fc..1c98c6090cf2 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -231,37 +231,6 @@ const uint32_t Arena::FirstThingOffsets[] = { #undef OFFSET -class GCCompartmentsIter { - private: - JSCompartment **it, **end; - - public: - GCCompartmentsIter(JSRuntime *rt) { - if (rt->gcCurrentCompartment) { - it = &rt->gcCurrentCompartment; - end = &rt->gcCurrentCompartment + 1; - } else { - it = rt->compartments.begin(); - end = rt->compartments.end(); - } - } - - bool done() const { return it == end; } - - void next() { - JS_ASSERT(!done()); - it++; - } - - JSCompartment *get() const { - JS_ASSERT(!done()); - return *it; - } - - operator JSCompartment *() const { return get(); } - JSCompartment *operator->() const { return get(); } -}; - #ifdef DEBUG void ArenaHeader::checkSynchronizedWithFreeList() const @@ -889,8 +858,7 @@ JS_FRIEND_API(bool) IsAboutToBeFinalized(const Cell *thing) { JSCompartment *thingCompartment = reinterpret_cast(thing)->compartment(); - JSRuntime *rt = thingCompartment->rt; - if (rt->gcCurrentCompartment != NULL && rt->gcCurrentCompartment != thingCompartment) + if (!thingCompartment->isCollecting()) return false; return !reinterpret_cast(thing)->isMarked(); } @@ -1032,8 +1000,7 @@ IsAddressableGCThing(JSRuntime *rt, uintptr_t w, if (!aheader->allocated()) return CGCT_FREEARENA; - JSCompartment *curComp = rt->gcCurrentCompartment; - if (curComp && curComp != aheader->compartment) + if (rt->gcRunning && !aheader->compartment->isCollecting()) return CGCT_OTHERCOMPARTMENT; AllocKind thingKind = aheader->getAllocKind(); @@ -1168,24 +1135,6 @@ MarkConservativeStackRoots(JSTracer *trc, bool useSavedRoots) void MarkStackRangeConservatively(JSTracer *trc, Value *beginv, Value *endv) { - /* - * Normally, the drainMarkStack phase of marking will never trace outside - * of the compartment currently being collected. However, conservative - * scanning during drainMarkStack (as is done for generators) can break - * this invariant. So we disable the compartment assertions in this - * situation. - */ - struct AutoSkipChecking { - JSRuntime *runtime; - JSCompartment *savedCompartment; - - AutoSkipChecking(JSRuntime *rt) - : runtime(rt), savedCompartment(rt->gcCheckCompartment) { - rt->gcCheckCompartment = NULL; - } - ~AutoSkipChecking() { runtime->gcCheckCompartment = savedCompartment; } - } as(trc->runtime); - const uintptr_t *begin = beginv->payloadWord(); const uintptr_t *end = endv->payloadWord(); #ifdef JS_NUNBOX32 @@ -1691,13 +1640,13 @@ ArenaLists::finalizeScripts(JSContext *cx) } static void -RunLastDitchGC(JSContext *cx, gcreason::Reason reason) +RunLastDitchGC(JSContext *cx, gcreason::Reason reason, bool full) { JSRuntime *rt = cx->runtime; /* The last ditch GC preserves all atoms. */ AutoKeepAtoms keep(rt); - GC(cx, rt->gcTriggerCompartment, GC_NORMAL, reason); + GC(cx, full, GC_NORMAL, reason); } /* static */ void * @@ -1712,7 +1661,8 @@ ArenaLists::refillFreeList(JSContext *cx, AllocKind thingKind) bool runGC = rt->gcIncrementalState != NO_INCREMENTAL && comp->gcBytes > comp->gcTriggerBytes; for (;;) { if (JS_UNLIKELY(runGC)) { - RunLastDitchGC(cx, gcreason::LAST_DITCH); + PrepareCompartmentForGC(comp); + RunLastDitchGC(cx, gcreason::LAST_DITCH, rt->gcFullIsNeeded); /* * The JSGC_END callback can legitimately allocate new GC @@ -2017,12 +1967,7 @@ void GCMarker::checkCompartment(void *p) { JS_ASSERT(started); - - Cell *cell = static_cast(p); - if (runtime->gcRunning && runtime->gcCurrentCompartment) - JS_ASSERT(cell->compartment() == runtime->gcCurrentCompartment); - else if (runtime->gcIncrementalCompartment) - JS_ASSERT(cell->compartment() == runtime->gcIncrementalCompartment); + JS_ASSERT(static_cast(p)->compartment()->isCollecting()); } #endif @@ -2111,44 +2056,9 @@ SetMarkStackLimit(JSRuntime *rt, size_t limit) } /* namespace js */ -#ifdef DEBUG -static void -EmptyMarkCallback(JSTracer *trc, void **thingp, JSGCTraceKind kind) -{ -} -#endif - static void gc_root_traversal(JSTracer *trc, const RootEntry &entry) { -#ifdef DEBUG - void *ptr; - if (entry.value.type == JS_GC_ROOT_GCTHING_PTR) { - ptr = *reinterpret_cast(entry.key); - } else { - Value *vp = reinterpret_cast(entry.key); - ptr = vp->isGCThing() ? vp->toGCThing() : NULL; - } - - if (ptr && !trc->runtime->gcCurrentCompartment) { - /* - * Use conservative machinery to find if ptr is a valid GC thing. - * We only do this during global GCs, to preserve the invariant - * that mark callbacks are not in place during compartment GCs. - */ - JSTracer checker; - JS_TracerInit(&checker, trc->runtime, EmptyMarkCallback); - ConservativeGCTest test = MarkIfGCThingWord(&checker, reinterpret_cast(ptr)); - if (test != CGCT_VALID && entry.value.name) { - fprintf(stderr, -"JS API usage error: the address passed to JS_AddNamedRoot currently holds an\n" -"invalid gcthing. This is usually caused by a missing call to JS_RemoveRoot.\n" -"The root's name is \"%s\".\n", - entry.value.name); - } - JS_ASSERT(test == CGCT_VALID); - } -#endif const char *name = entry.value.name ? entry.value.name : "root"; if (entry.value.type == JS_GC_ROOT_GCTHING_PTR) MarkGCThingRoot(trc, reinterpret_cast(entry.key), name); @@ -2327,9 +2237,12 @@ MarkRuntime(JSTracer *trc, bool useSavedRoots = false) { JSRuntime *rt = trc->runtime; JS_ASSERT(trc->callback != GCMarker::GrayCallback); - if (rt->gcCurrentCompartment) { - for (CompartmentsIter c(rt); !c.done(); c.next()) - c->markCrossCompartmentWrappers(trc); + + if (IS_GC_MARKING_TRACER(trc) && !rt->gcIsFull) { + for (CompartmentsIter c(rt); !c.done(); c.next()) { + if (!c->isCollecting()) + c->markCrossCompartmentWrappers(trc); + } Debugger::markCrossCompartmentDebuggerObjectReferents(trc); } @@ -2356,7 +2269,11 @@ MarkRuntime(JSTracer *trc, bool useSavedRoots = false) for (ContextIter acx(rt); !acx.done(); acx.next()) acx->mark(trc); - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { + /* We can't use GCCompartmentsIter if we're called from TraceRuntime. */ + for (CompartmentsIter c(rt); !c.done(); c.next()) { + if (IS_GC_MARKING_TRACER(trc) && !c->isCollecting()) + continue; + if (c->activeAnalysis) c->markTypes(trc); @@ -2413,7 +2330,7 @@ TriggerGC(JSRuntime *rt, gcreason::Reason reason) /* Trigger the GC when it is safe to call an operation callback. */ rt->gcIsNeeded = true; - rt->gcTriggerCompartment = NULL; + rt->gcFullIsNeeded = true; rt->gcTriggerReason = reason; rt->triggerOperationCallback(); } @@ -2435,19 +2352,16 @@ TriggerCompartmentGC(JSCompartment *comp, gcreason::Reason reason) return; } - if (rt->gcIsNeeded) { - /* If we need to GC more than one compartment, run a full GC. */ - if (rt->gcTriggerCompartment != comp) - rt->gcTriggerCompartment = NULL; + PrepareCompartmentForGC(comp); + + if (rt->gcIsNeeded) return; - } /* * Trigger the GC when it is safe to call an operation callback on any * thread. */ rt->gcIsNeeded = true; - rt->gcTriggerCompartment = comp; rt->gcTriggerReason = reason; rt->triggerOperationCallback(); } @@ -2459,14 +2373,13 @@ MaybeGC(JSContext *cx) JS_ASSERT(rt->onOwnerThread()); if (rt->gcZeal() == ZealAllocValue || rt->gcZeal() == ZealPokeValue) { - GC(cx, NULL, GC_NORMAL, gcreason::MAYBEGC); + GC(cx, true, GC_NORMAL, gcreason::MAYBEGC); return; } JSCompartment *comp = cx->compartment; if (rt->gcIsNeeded) { - GCSlice(cx, (comp == rt->gcTriggerCompartment) ? comp : NULL, - GC_NORMAL, gcreason::MAYBEGC); + GCSlice(cx, rt->gcFullIsNeeded, GC_NORMAL, gcreason::MAYBEGC); return; } @@ -2474,12 +2387,14 @@ MaybeGC(JSContext *cx) comp->gcBytes >= 3 * (comp->gcTriggerBytes / 4) && rt->gcIncrementalState == NO_INCREMENTAL) { - GCSlice(cx, NULL, GC_NORMAL, gcreason::MAYBEGC); + PrepareCompartmentForGC(comp); + GCSlice(cx, false, GC_NORMAL, gcreason::MAYBEGC); return; } if (comp->gcMallocAndFreeBytes > comp->gcTriggerMallocAndFreeBytes) { - GCSlice(cx, comp, GC_NORMAL, gcreason::MAYBEGC); + PrepareCompartmentForGC(comp); + GCSlice(cx, false, GC_NORMAL, gcreason::MAYBEGC); return; } @@ -2493,7 +2408,7 @@ MaybeGC(JSContext *cx) if (rt->gcChunkAllocationSinceLastGC || rt->gcNumArenasFreeCommitted > FreeCommittedArenasThreshold) { - GCSlice(cx, NULL, GC_SHRINK, gcreason::MAYBEGC); + GCSlice(cx, true, GC_SHRINK, gcreason::MAYBEGC); } else { rt->gcNextFullGCTime = now + GC_IDLE_FULL_SPAN; } @@ -2890,6 +2805,12 @@ GCHelperThread::doSweep() #endif /* JS_THREADSAFE */ +void +PrepareCompartmentForGC(JSCompartment *comp) +{ + comp->scheduleGC(); +} + } /* namespace js */ static bool @@ -2938,10 +2859,15 @@ SweepCompartments(JSContext *cx, JSGCInvocationKind gckind) } static void -PurgeRuntime(JSRuntime *rt) +PurgeRuntime(JSTracer *trc) { - for (GCCompartmentsIter c(rt); !c.done(); c.next()) - c->purge(); + JSRuntime *rt = trc->runtime; + + for (CompartmentsIter c(rt); !c.done(); c.next()) { + /* We can be called from StartVerifyBarriers with a non-GC marker. */ + if (c->isCollecting() || !IS_GC_MARKING_TRACER(trc)) + c->purge(); + } rt->tempLifoAlloc.freeUnused(); rt->gsnCache.purge(); @@ -2974,7 +2900,7 @@ BeginMarkPhase(JSRuntime *rt) */ { gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_PURGE); - PurgeRuntime(rt); + PurgeRuntime(gcmarker); } /* @@ -3049,9 +2975,9 @@ EndMarkPhase(JSContext *cx) #ifdef DEBUG /* Make sure that we didn't mark an object in another compartment */ - if (rt->gcCurrentCompartment) { + if (!rt->gcIsFull) { for (CompartmentsIter c(rt); !c.done(); c.next()) { - JS_ASSERT_IF(c != rt->gcCurrentCompartment && c != rt->atomsCompartment, + JS_ASSERT_IF(!c->isCollecting() && c != rt->atomsCompartment, c->arenas.checkArenaListAllUnmarked()); } } @@ -3121,10 +3047,12 @@ ValidateIncrementalMarking(JSContext *cx) js_free(entry); for (size_t i = 0; i < ArenasPerChunk; i++) { + if (chunk->decommittedArenas.get(i)) + continue; Arena *arena = &chunk->arenas[i]; if (!arena->aheader.allocated()) continue; - if (rt->gcCurrentCompartment && arena->aheader.compartment != rt->gcCurrentCompartment) + if (!arena->aheader.compartment->isCollecting()) continue; if (arena->aheader.allocatedDuringIncremental) continue; @@ -3198,13 +3126,13 @@ SweepPhase(JSContext *cx, JSGCInvocationKind gckind) /* Collect watch points associated with unreachable objects. */ WatchpointMap::sweepAll(rt); - if (!rt->gcCurrentCompartment) - Debugger::sweepAll(cx); + /* Detach unreachable debuggers and global objects from each other. */ + Debugger::sweepAll(cx); { gcstats::AutoPhase ap(rt->gcStats, gcstats::PHASE_SWEEP_COMPARTMENTS); - bool releaseTypes = !rt->gcCurrentCompartment && ReleaseObservedTypes(rt); + bool releaseTypes = rt->gcIsFull && ReleaseObservedTypes(rt); for (GCCompartmentsIter c(rt); !c.done(); c.next()) c->sweep(cx, releaseTypes); } @@ -3258,7 +3186,7 @@ SweepPhase(JSContext *cx, JSGCInvocationKind gckind) * This removes compartments from rt->compartment, so we do it last to make * sure we don't miss sweeping any compartments. */ - if (!rt->gcCurrentCompartment) + if (rt->gcIsFull) SweepCompartments(cx, gckind); #ifndef JS_THREADSAFE @@ -3324,7 +3252,7 @@ class AutoHeapSession { /* ...while this class is to be used only for garbage collection. */ class AutoGCSession : AutoHeapSession { public: - explicit AutoGCSession(JSRuntime *rt, JSCompartment *comp); + explicit AutoGCSession(JSRuntime *rt, bool full); ~AutoGCSession(); }; @@ -3343,14 +3271,21 @@ AutoHeapSession::~AutoHeapSession() runtime->gcRunning = false; } -AutoGCSession::AutoGCSession(JSRuntime *rt, JSCompartment *comp) +AutoGCSession::AutoGCSession(JSRuntime *rt, bool full) : AutoHeapSession(rt) { - JS_ASSERT(!runtime->gcCurrentCompartment); - runtime->gcCurrentCompartment = comp; + rt->gcIsFull = full; + DebugOnly any = false; + for (CompartmentsIter c(rt); !c.done(); c.next()) { + if (full || c->isGCScheduled()) { + c->setCollecting(true); + any = true; + } + } + JS_ASSERT(any); runtime->gcIsNeeded = false; - runtime->gcTriggerCompartment = NULL; + runtime->gcFullIsNeeded = false; runtime->gcInterFrameGC = true; runtime->gcNumber++; @@ -3364,7 +3299,10 @@ AutoGCSession::AutoGCSession(JSRuntime *rt, JSCompartment *comp) AutoGCSession::~AutoGCSession() { - runtime->gcCurrentCompartment = NULL; + runtime->gcIsFull = false; + for (GCCompartmentsIter c(runtime); !c.done(); c.next()) + c->setCollecting(false); + runtime->gcNextFullGCTime = PRMJ_Now() + GC_IDLE_FULL_SPAN; runtime->gcChunkAllocationSinceLastGC = false; } @@ -3375,18 +3313,16 @@ ResetIncrementalGC(JSRuntime *rt, const char *reason) if (rt->gcIncrementalState == NO_INCREMENTAL) return; - for (CompartmentsIter c(rt); !c.done(); c.next()) { - if (!rt->gcIncrementalCompartment || rt->gcIncrementalCompartment == c) - c->needsBarrier_ = false; + for (CompartmentsIter c(rt); !c.done(); c.next()) + c->needsBarrier_ = false; - JS_ASSERT(!c->needsBarrier_); - } - - rt->gcIncrementalCompartment = NULL; + rt->gcIncrementalIsFull = false; rt->gcMarker.reset(); rt->gcMarker.stop(); rt->gcIncrementalState = NO_INCREMENTAL; + JS_ASSERT(!rt->gcStrictCompartmentChecking); + rt->gcStats.reset(reason); } @@ -3464,8 +3400,8 @@ IncrementalGCSlice(JSContext *cx, int64_t budget, JSGCInvocationKind gckind) gc::State initialState = rt->gcIncrementalState; if (rt->gcIncrementalState == NO_INCREMENTAL) { - JS_ASSERT(!rt->gcIncrementalCompartment); - rt->gcIncrementalCompartment = rt->gcCurrentCompartment; + JS_ASSERT(!rt->gcIncrementalIsFull); + rt->gcIncrementalIsFull = rt->gcIsFull; rt->gcIncrementalState = MARK_ROOTS; rt->gcLastMarkSlice = false; } @@ -3511,7 +3447,7 @@ IncrementalGCSlice(JSContext *cx, int64_t budget, JSGCInvocationKind gckind) /* JIT code was already discarded during sweeping. */ - rt->gcIncrementalCompartment = NULL; + rt->gcIncrementalIsFull = false; rt->gcIncrementalState = NO_INCREMENTAL; } @@ -3551,7 +3487,7 @@ IsIncrementalGCSafe(JSRuntime *rt) if (rt->gcKeepAtoms) return IncrementalSafety::Unsafe("gcKeepAtoms set"); - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { + for (CompartmentsIter c(rt); !c.done(); c.next()) { if (c->activeAnalysis) return IncrementalSafety::Unsafe("activeAnalysis set"); } @@ -3587,19 +3523,17 @@ BudgetIncrementalGC(JSRuntime *rt, int64_t *budget) return; #endif - if (rt->gcIncrementalState != NO_INCREMENTAL && - rt->gcCurrentCompartment != rt->gcIncrementalCompartment) - { - ResetIncrementalGC(rt, "compartment change"); - return; - } - for (CompartmentsIter c(rt); !c.done(); c.next()) { if (c->gcBytes > c->gcTriggerBytes) { *budget = SliceBudget::Unlimited; rt->gcStats.nonincremental("allocation trigger"); return; } + + if (c->isCollecting() != c->needsBarrier()) { + ResetIncrementalGC(rt, "compartment change"); + return; + } } } @@ -3610,18 +3544,18 @@ BudgetIncrementalGC(JSRuntime *rt, int64_t *budget) * marking implementation. */ static JS_NEVER_INLINE void -GCCycle(JSContext *cx, JSCompartment *comp, int64_t budget, JSGCInvocationKind gckind) +GCCycle(JSContext *cx, bool full, int64_t budget, JSGCInvocationKind gckind) { JSRuntime *rt = cx->runtime; - JS_ASSERT_IF(comp, comp != rt->atomsCompartment); - JS_ASSERT_IF(comp, rt->gcMode != JSGC_MODE_GLOBAL); + JS_ASSERT_IF(!full, !rt->atomsCompartment->isCollecting()); + JS_ASSERT_IF(!full, rt->gcMode != JSGC_MODE_GLOBAL); /* Recursive GC is no-op. */ if (rt->gcRunning) return; - AutoGCSession gcsession(rt, comp); + AutoGCSession gcsession(rt, full); /* Don't GC if we are reporting an OOM. */ if (rt->inOOMReport) @@ -3689,7 +3623,7 @@ IsDeterministicGCReason(gcreason::Reason reason) #endif static void -Collect(JSContext *cx, JSCompartment *comp, int64_t budget, +Collect(JSContext *cx, bool full, int64_t budget, JSGCInvocationKind gckind, gcreason::Reason reason) { JSRuntime *rt = cx->runtime; @@ -3726,13 +3660,13 @@ Collect(JSContext *cx, JSCompartment *comp, int64_t budget, RecordNativeStackTopForGC(rt); if (rt->gcMode == JSGC_MODE_GLOBAL) - comp = NULL; + full = true; /* This is a heuristic to avoid resets. */ - if (rt->gcIncrementalState != NO_INCREMENTAL && !rt->gcIncrementalCompartment) - comp = NULL; + if (rt->gcIncrementalState != NO_INCREMENTAL && rt->gcIncrementalIsFull) + full = true; - gcstats::AutoGCSlice agc(rt->gcStats, comp, reason); + gcstats::AutoGCSlice agc(rt->gcStats, full, reason); do { /* @@ -3749,7 +3683,7 @@ Collect(JSContext *cx, JSCompartment *comp, int64_t budget, /* Lock out other GC allocator and collector invocations. */ AutoLockGC lock(rt); rt->gcPoke = false; - GCCycle(cx, comp, budget, gckind); + GCCycle(cx, full, budget, gckind); } if (rt->gcIncrementalState == NO_INCREMENTAL) { @@ -3768,15 +3702,15 @@ Collect(JSContext *cx, JSCompartment *comp, int64_t budget, namespace js { void -GC(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind, gcreason::Reason reason) +GC(JSContext *cx, bool full, JSGCInvocationKind gckind, gcreason::Reason reason) { - Collect(cx, comp, SliceBudget::Unlimited, gckind, reason); + Collect(cx, full, SliceBudget::Unlimited, gckind, reason); } void -GCSlice(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind, gcreason::Reason reason) +GCSlice(JSContext *cx, bool full, JSGCInvocationKind gckind, gcreason::Reason reason) { - Collect(cx, comp, cx->runtime->gcSliceBudget, gckind, reason); + Collect(cx, full, cx->runtime->gcSliceBudget, gckind, reason); } void @@ -3989,11 +3923,9 @@ RunDebugGC(JSContext *cx) * If rt->gcDebugCompartmentGC is true, only GC the current * compartment. But don't GC the atoms compartment. */ - rt->gcTriggerCompartment = rt->gcDebugCompartmentGC ? cx->compartment : NULL; - if (rt->gcTriggerCompartment == rt->atomsCompartment) - rt->gcTriggerCompartment = NULL; - - RunLastDitchGC(cx, gcreason::DEBUG_GC); + if (rt->gcDebugCompartmentGC) + PrepareCompartmentForGC(cx->compartment); + RunLastDitchGC(cx, gcreason::DEBUG_GC, !rt->gcDebugCompartmentGC); #endif } @@ -4241,8 +4173,6 @@ StartVerifyBarriers(JSContext *cx) for (CompartmentsIter c(rt); !c.done(); c.next()) c->discardJitCode(cx); - PurgeRuntime(rt); - VerifyTracer *trc = new (js_malloc(sizeof(VerifyTracer))) VerifyTracer; rt->gcNumber++; @@ -4251,6 +4181,8 @@ StartVerifyBarriers(JSContext *cx) JS_TracerInit(trc, rt, AccumulateEdge); + PurgeRuntime(trc); + const size_t size = 64 * 1024 * 1024; trc->root = (VerifyNode *)js_malloc(size); JS_ASSERT(trc->root); diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 159a37647ebc..6821834c373f 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -1383,6 +1383,9 @@ MaybeGC(JSContext *cx); extern void ShrinkGCBuffers(JSRuntime *rt); +extern void +PrepareCompartmentForGC(JSCompartment *comp); + /* * Kinds of js_GC invocation. */ @@ -1396,10 +1399,10 @@ typedef enum JSGCInvocationKind { /* Pass NULL for |comp| to get a full GC. */ extern void -GC(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind, js::gcreason::Reason reason); +GC(JSContext *cx, bool full, JSGCInvocationKind gckind, js::gcreason::Reason reason); extern void -GCSlice(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind, js::gcreason::Reason reason); +GCSlice(JSContext *cx, bool full, JSGCInvocationKind gckind, js::gcreason::Reason reason); extern void GCDebugSlice(JSContext *cx, int64_t objCount); diff --git a/js/src/jsgcinlines.h b/js/src/jsgcinlines.h index feb489a0d8ae..cbab72da2207 100644 --- a/js/src/jsgcinlines.h +++ b/js/src/jsgcinlines.h @@ -388,6 +388,38 @@ class CellIter : public CellIterImpl inline void EmptyArenaOp(Arena *arena) {} inline void EmptyCellOp(Cell *t) {} +class GCCompartmentsIter { + private: + JSCompartment **it, **end; + + public: + GCCompartmentsIter(JSRuntime *rt) { + JS_ASSERT(rt->gcRunning); + it = rt->compartments.begin(); + end = rt->compartments.end(); + if (!(*it)->isCollecting()) + next(); + JS_ASSERT(it < end); + } + + bool done() const { return it == end; } + + void next() { + JS_ASSERT(!done()); + do { + it++; + } while (it != end && !(*it)->isCollecting()); + } + + JSCompartment *get() const { + JS_ASSERT(!done()); + return *it; + } + + operator JSCompartment *() const { return get(); } + JSCompartment *operator->() const { return get(); } +}; + /* * Allocates a new GC thing. After a successful allocation the caller must * fully initialize the thing before calling any function that can potentially diff --git a/js/src/jsgcmark.cpp b/js/src/jsgcmark.cpp index af2c0cc9888f..06b087b8be88 100644 --- a/js/src/jsgcmark.cpp +++ b/js/src/jsgcmark.cpp @@ -77,13 +77,20 @@ CheckMarkedThing(JSTracer *trc, T *thing) { JS_ASSERT(trc); JS_ASSERT(thing); + JS_ASSERT(thing->compartment()); + JS_ASSERT(thing->compartment()->rt == trc->runtime); JS_ASSERT(trc->debugPrinter || trc->debugPrintArg); - JS_ASSERT_IF(trc->runtime->gcCurrentCompartment, IS_GC_MARKING_TRACER(trc)); + + DebugOnly rt = trc->runtime; + + JS_ASSERT_IF(rt->gcIsFull, IS_GC_MARKING_TRACER(trc)); + JS_ASSERT_IF(thing->compartment()->requireGCTracer(), IS_GC_MARKING_TRACER(trc)); JS_ASSERT(thing->isAligned()); - JS_ASSERT(thing->compartment()); - JS_ASSERT(thing->compartment()->rt == trc->runtime); + JS_ASSERT_IF(rt->gcStrictCompartmentChecking, + thing->compartment()->isCollecting() || + thing->compartment() == rt->atomsCompartment); } template @@ -95,22 +102,15 @@ MarkInternal(JSTracer *trc, T **thingp) CheckMarkedThing(trc, thing); - JSRuntime *rt = trc->runtime; - - JS_ASSERT_IF(rt->gcCheckCompartment, - thing->compartment() == rt->gcCheckCompartment || - thing->compartment() == rt->atomsCompartment); - /* * Don't mark things outside a compartment if we are in a per-compartment * GC. */ - if (!rt->gcCurrentCompartment || thing->compartment() == rt->gcCurrentCompartment) { - if (!trc->callback) { + if (!trc->callback) { + if (thing->compartment()->isCollecting()) PushMarkStack(static_cast(trc), thing); - } else { - trc->callback(trc, (void **)thingp, GetGCThingTraceKind(thing)); - } + } else { + trc->callback(trc, (void **)thingp, GetGCThingTraceKind(thing)); } #ifdef DEBUG @@ -393,17 +393,30 @@ MarkObjectSlots(JSTracer *trc, JSObject *obj, uint32_t start, uint32_t nslots) } } +void +MarkCrossCompartmentObjectUnbarriered(JSTracer *trc, JSObject **obj, const char *name) +{ + if (IS_GC_MARKING_TRACER(trc) && !(*obj)->compartment()->isCollecting()) + return; + + MarkObjectUnbarriered(trc, obj, name); +} + +void +MarkCrossCompartmentScriptUnbarriered(JSTracer *trc, JSScript **script, const char *name) +{ + if (IS_GC_MARKING_TRACER(trc) && !(*script)->compartment()->isCollecting()) + return; + + MarkScriptUnbarriered(trc, script, name); +} + void MarkCrossCompartmentSlot(JSTracer *trc, HeapSlot *s, const char *name) { if (s->isMarkable()) { Cell *cell = (Cell *)s->toGCThing(); - JSRuntime *rt = trc->runtime; - if (rt->gcCurrentCompartment && cell->compartment() != rt->gcCurrentCompartment) - return; - - /* In case we're called from a write barrier. */ - if (rt->gcIncrementalCompartment && cell->compartment() != rt->gcIncrementalCompartment) + if (IS_GC_MARKING_TRACER(trc) && !cell->compartment()->isCollecting()) return; MarkSlot(trc, s, name); @@ -428,14 +441,12 @@ MarkValueUnbarriered(JSTracer *trc, Value *v, const char *name) /*** Push Mark Stack ***/ -#define JS_COMPARTMENT_ASSERT(rt, thing) \ - JS_ASSERT_IF((rt)->gcCurrentCompartment, \ - (thing)->compartment() == (rt)->gcCurrentCompartment); +#define JS_COMPARTMENT_ASSERT(rt, thing) \ + JS_ASSERT((thing)->compartment()->isCollecting()) -#define JS_COMPARTMENT_ASSERT_STR(rt, thing) \ - JS_ASSERT_IF((rt)->gcCurrentCompartment, \ - (thing)->compartment() == (rt)->gcCurrentCompartment || \ - (thing)->compartment() == (rt)->atomsCompartment); +#define JS_COMPARTMENT_ASSERT_STR(rt, thing) \ + JS_ASSERT((thing)->compartment()->isCollecting() || \ + (thing)->compartment() == (rt)->atomsCompartment); static void PushMarkStack(GCMarker *gcmarker, JSXML *thing) @@ -1105,9 +1116,10 @@ GCMarker::drainMarkStack(SliceBudget &budget) struct AutoCheckCompartment { JSRuntime *runtime; AutoCheckCompartment(JSRuntime *rt) : runtime(rt) { - runtime->gcCheckCompartment = runtime->gcCurrentCompartment; + JS_ASSERT(!rt->gcStrictCompartmentChecking); + runtime->gcStrictCompartmentChecking = true; } - ~AutoCheckCompartment() { runtime->gcCheckCompartment = NULL; } + ~AutoCheckCompartment() { runtime->gcStrictCompartmentChecking = false; } } acc(rt); #endif diff --git a/js/src/jsgcmark.h b/js/src/jsgcmark.h index 51f86afb5dbd..dd243c285780 100644 --- a/js/src/jsgcmark.h +++ b/js/src/jsgcmark.h @@ -126,6 +126,12 @@ MarkArraySlots(JSTracer *trc, size_t len, HeapSlot *vec, const char *name); void MarkObjectSlots(JSTracer *trc, JSObject *obj, uint32_t start, uint32_t nslots); +void +MarkCrossCompartmentObjectUnbarriered(JSTracer *trc, JSObject **obj, const char *name); + +void +MarkCrossCompartmentScriptUnbarriered(JSTracer *trc, JSScript **script, const char *name); + /* * Mark a value that may be in a different compartment from the compartment * being GC'd. (Although it won't be marked if it's in the wrong compartment.) diff --git a/js/src/jspropertytree.cpp b/js/src/jspropertytree.cpp index f5fbf6c57fd6..1e62a724727e 100644 --- a/js/src/jspropertytree.cpp +++ b/js/src/jspropertytree.cpp @@ -47,6 +47,7 @@ #include "jspropertytree.h" #include "jsscope.h" +#include "jsgcinlines.h" #include "jsobjinlines.h" #include "jsscopeinlines.h" @@ -354,10 +355,7 @@ js::PropertyTree::dumpShapes(JSContext *cx) JSRuntime *rt = cx->runtime; fprintf(dumpfp, "rt->gcNumber = %lu", (unsigned long)rt->gcNumber); - for (CompartmentsIter c(rt); !c.done(); c.next()) { - if (rt->gcCurrentCompartment != NULL && rt->gcCurrentCompartment != c) - continue; - + for (gc::GCCompartmentsIter c(rt); !c.done(); c.next()) { fprintf(dumpfp, "*** Compartment %p ***\n", (void *)c.get()); /* diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 379a2c68ff4f..da751793142e 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1828,8 +1828,7 @@ JSScript::clearTraps(JSContext *cx) void JSScript::markChildren(JSTracer *trc) { - JS_ASSERT_IF(trc->runtime->gcCheckCompartment, - compartment() == trc->runtime->gcCheckCompartment); + JS_ASSERT_IF(trc->runtime->gcStrictCompartmentChecking, compartment()->isCollecting()); for (uint32_t i = 0; i < natoms; ++i) { if (atoms[i]) diff --git a/js/src/jswatchpoint.cpp b/js/src/jswatchpoint.cpp index c9b93df12691..d0f592ce69cf 100644 --- a/js/src/jswatchpoint.cpp +++ b/js/src/jswatchpoint.cpp @@ -162,13 +162,8 @@ bool WatchpointMap::markAllIteratively(JSTracer *trc) { JSRuntime *rt = trc->runtime; - if (rt->gcCurrentCompartment) { - WatchpointMap *wpmap = rt->gcCurrentCompartment->watchpointMap; - return wpmap && wpmap->markIteratively(trc); - } - bool mutated = false; - for (CompartmentsIter c(rt); !c.done(); c.next()) { + for (GCCompartmentsIter c(rt); !c.done(); c.next()) { if (c->watchpointMap) mutated |= c->watchpointMap->markIteratively(trc); } @@ -227,14 +222,9 @@ WatchpointMap::markAll(JSTracer *trc) void WatchpointMap::sweepAll(JSRuntime *rt) { - if (rt->gcCurrentCompartment) { - if (WatchpointMap *wpmap = rt->gcCurrentCompartment->watchpointMap) + for (GCCompartmentsIter c(rt); !c.done(); c.next()) { + if (WatchpointMap *wpmap = c->watchpointMap) wpmap->sweep(); - } else { - for (CompartmentsIter c(rt); !c.done(); c.next()) { - if (WatchpointMap *wpmap = c->watchpointMap) - wpmap->sweep(); - } } } diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 5b32caa9889b..528292e297df 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -1275,9 +1275,6 @@ Debugger::onSingleStep(JSContext *cx, Value *vp) void Debugger::markKeysInCompartment(JSTracer *tracer) { - JSCompartment *comp = tracer->runtime->gcCurrentCompartment; - JS_ASSERT(comp); - /* * WeakMap::Range is deliberately private, to discourage C++ code from * enumerating WeakMap keys. However in this case we need access, so we @@ -1288,21 +1285,17 @@ Debugger::markKeysInCompartment(JSTracer *tracer) const ObjectMap &objStorage = objects; for (ObjectMap::Range r = objStorage.all(); !r.empty(); r.popFront()) { const HeapPtrObject &key = r.front().key; - if (key->compartment() == comp && IsAboutToBeFinalized(key)) { - HeapPtrObject tmp(key); - gc::MarkObject(tracer, &tmp, "cross-compartment WeakMap key"); - JS_ASSERT(tmp == key); - } + HeapPtrObject tmp(key); + gc::MarkObject(tracer, &tmp, "cross-compartment WeakMap key"); + JS_ASSERT(tmp == key); } const ObjectMap &envStorage = environments; for (ObjectMap::Range r = envStorage.all(); !r.empty(); r.popFront()) { const HeapPtrObject &key = r.front().key; - if (key->compartment() == comp && IsAboutToBeFinalized(key)) { - HeapPtrObject tmp(key); - js::gc::MarkObject(tracer, &tmp, "cross-compartment WeakMap key"); - JS_ASSERT(tmp == key); - } + HeapPtrObject tmp(key); + js::gc::MarkObject(tracer, &tmp, "cross-compartment WeakMap key"); + JS_ASSERT(tmp == key); } typedef HashMap, RuntimeAllocPolicy> @@ -1310,11 +1303,9 @@ Debugger::markKeysInCompartment(JSTracer *tracer) const ScriptMap &scriptStorage = scripts; for (ScriptMap::Range r = scriptStorage.all(); !r.empty(); r.popFront()) { const HeapPtrScript &key = r.front().key; - if (key->compartment() == comp && IsAboutToBeFinalized(key)) { - HeapPtrScript tmp(key); - gc::MarkScript(tracer, &tmp, "cross-compartment WeakMap key"); - JS_ASSERT(tmp == key); - } + HeapPtrScript tmp(key); + gc::MarkScript(tracer, &tmp, "cross-compartment WeakMap key"); + JS_ASSERT(tmp == key); } } @@ -1323,19 +1314,17 @@ Debugger::markKeysInCompartment(JSTracer *tracer) * discovered that the WeakMap was live; that is, some object containing the * WeakMap was marked during mark phase. * - * However, during single-compartment GC, we have to do something about - * cross-compartment WeakMaps in other compartments. Since those compartments - * aren't being GC'd, the WeakMaps definitely will not be found during mark - * phase. If their keys and values might need to be marked, we have to do it - * manually. + * However, during compartment GC, we have to do something about + * cross-compartment WeakMaps in non-GC'd compartments. If their keys and values + * might need to be marked, we have to do it manually. * - * Each Debugger object keeps two cross-compartment WeakMaps: objects and - * scripts. Both have the nice property that all their values are in the - * same compartment as the Debugger object, so we only need to mark the - * keys. We must simply mark all keys that are in the compartment being GC'd. + * Each Debugger object keeps three cross-compartment WeakMaps: objects, script, + * and environments. They have the nice property that all their values are in + * the same compartment as the Debugger object, so we only need to mark the + * keys. We must simply mark all keys that are in a compartment being GC'd. * * We must scan all Debugger objects regardless of whether they *currently* - * have any debuggees in the compartment being GC'd, because the WeakMap + * have any debuggees in a compartment being GC'd, because the WeakMap * entries persist even when debuggees are removed. * * This happens during the initial mark phase, not iterative marking, because @@ -1345,7 +1334,6 @@ void Debugger::markCrossCompartmentDebuggerObjectReferents(JSTracer *tracer) { JSRuntime *rt = tracer->runtime; - JSCompartment *comp = rt->gcCurrentCompartment; /* * Mark all objects in comp that are referents of Debugger.Objects in other @@ -1353,7 +1341,7 @@ Debugger::markCrossCompartmentDebuggerObjectReferents(JSTracer *tracer) */ for (JSCList *p = &rt->debuggerList; (p = JS_NEXT_LINK(p)) != &rt->debuggerList;) { Debugger *dbg = Debugger::fromLinks(p); - if (dbg->object->compartment() != comp) + if (!dbg->object->compartment()->isCollecting()) dbg->markKeysInCompartment(tracer); } } @@ -1378,18 +1366,8 @@ Debugger::markAllIteratively(GCMarker *trc) * convoluted since the easiest way to find them is via their debuggees. */ JSRuntime *rt = trc->runtime; - JSCompartment *comp = rt->gcCurrentCompartment; - for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); c++) { - JSCompartment *dc = *c; - - /* - * If this is a single-compartment GC, no compartment can debug itself, so skip - * |comp|. If it's a global GC, then search every compartment. - */ - if (comp && dc == comp) - continue; - - const GlobalObjectSet &debuggees = dc->getDebuggees(); + for (CompartmentsIter c(rt); !c.done(); c.next()) { + const GlobalObjectSet &debuggees = c->getDebuggees(); for (GlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) { GlobalObject *global = r.front(); if (IsAboutToBeFinalized(global)) @@ -1411,7 +1389,7 @@ Debugger::markAllIteratively(GCMarker *trc) * - it actually has hooks that might be called */ HeapPtrObject &dbgobj = dbg->toJSObjectRef(); - if (comp && comp != dbgobj->compartment()) + if (!dbgobj->compartment()->isCollecting()) continue; bool dbgMarked = !IsAboutToBeFinalized(dbgobj); @@ -1487,20 +1465,15 @@ void Debugger::sweepAll(JSContext *cx) { JSRuntime *rt = cx->runtime; - JS_ASSERT(!rt->gcCurrentCompartment); for (JSCList *p = &rt->debuggerList; (p = JS_NEXT_LINK(p)) != &rt->debuggerList;) { Debugger *dbg = Debugger::fromLinks(p); if (IsAboutToBeFinalized(dbg->object)) { /* - * dbg is being GC'd. Detach it from its debuggees. In the case of - * runtime-wide GC, the debuggee might be GC'd too. Since detaching - * requires access to both objects, this must be done before - * finalize time. However, in a per-compartment GC, it is - * impossible for both objects to be GC'd (since they are in - * different compartments), so in that case we just wait for - * Debugger::finalize. + * dbg is being GC'd. Detach it from its debuggees. The debuggee + * might be GC'd too. Since detaching requires access to both + * objects, this must be done before finalize time. */ for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) dbg->removeDebuggeeGlobal(cx, e.front(), NULL, &e); @@ -1535,15 +1508,7 @@ Debugger::finalize(JSContext *cx, JSObject *obj) Debugger *dbg = fromJSObject(obj); if (!dbg) return; - if (!dbg->debuggees.empty()) { - /* - * This happens only during per-compartment GC. See comment in - * Debugger::sweepAll. - */ - JS_ASSERT(cx->runtime->gcCurrentCompartment == dbg->object->compartment()); - for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) - dbg->removeDebuggeeGlobal(cx, e.front(), NULL, &e); - } + JS_ASSERT(dbg->debuggees.empty()); cx->delete_(dbg); } @@ -2183,12 +2148,10 @@ SetScriptReferent(JSObject *obj, JSScript *script) static void DebuggerScript_trace(JSTracer *trc, JSObject *obj) { - if (!trc->runtime->gcCurrentCompartment) { - /* This comes from a private pointer, so no barrier needed. */ - if (JSScript *script = GetScriptReferent(obj)) { - MarkScriptUnbarriered(trc, &script, "Debugger.Script referent"); - obj->setPrivateUnbarriered(script); - } + /* This comes from a private pointer, so no barrier needed. */ + if (JSScript *script = GetScriptReferent(obj)) { + MarkCrossCompartmentScriptUnbarriered(trc, &script, "Debugger.Script referent"); + obj->setPrivateUnbarriered(script); } } @@ -3315,15 +3278,13 @@ static JSFunctionSpec DebuggerFrame_methods[] = { static void DebuggerObject_trace(JSTracer *trc, JSObject *obj) { - if (!trc->runtime->gcCurrentCompartment) { - /* - * There is a barrier on private pointers, so the Unbarriered marking - * is okay. - */ - if (JSObject *referent = (JSObject *) obj->getPrivate()) { - MarkObjectUnbarriered(trc, &referent, "Debugger.Object referent"); - obj->setPrivateUnbarriered(referent); - } + /* + * There is a barrier on private pointers, so the Unbarriered marking + * is okay. + */ + if (JSObject *referent = (JSObject *) obj->getPrivate()) { + MarkCrossCompartmentObjectUnbarriered(trc, &referent, "Debugger.Object referent"); + obj->setPrivateUnbarriered(referent); } } @@ -3959,15 +3920,13 @@ static JSFunctionSpec DebuggerObject_methods[] = { static void DebuggerEnv_trace(JSTracer *trc, JSObject *obj) { - if (!trc->runtime->gcCurrentCompartment) { - /* - * There is a barrier on private pointers, so the Unbarriered marking - * is okay. - */ - if (Env *referent = (JSObject *) obj->getPrivate()) { - MarkObjectUnbarriered(trc, &referent, "Debugger.Environment referent"); - obj->setPrivateUnbarriered(referent); - } + /* + * There is a barrier on private pointers, so the Unbarriered marking + * is okay. + */ + if (Env *referent = (JSObject *) obj->getPrivate()) { + MarkCrossCompartmentObjectUnbarriered(trc, &referent, "Debugger.Environment referent"); + obj->setPrivateUnbarriered(referent); } }