From 5992bb7f5573c49af3d8bf571117fece64c59d77 Mon Sep 17 00:00:00 2001 From: Gregor Wagner Date: Fri, 7 Jan 2011 23:44:57 -0800 Subject: [PATCH] Enable per-compartment garbage collection (bug 605662, r=gal). CLOSED TREE --- dom/base/nsJSEnvironment.cpp | 20 +- js/src/jsapi.cpp | 73 +----- js/src/jsatom.cpp | 4 +- js/src/jscntxt.cpp | 6 +- js/src/jscntxt.h | 11 +- js/src/jscompartment.cpp | 22 +- js/src/jscompartment.h | 11 +- js/src/jsdbgapi.cpp | 2 +- js/src/jsgc.cpp | 306 +++++++++++++++++++++++--- js/src/jsgc.h | 33 ++- js/src/jsgcinlines.h | 75 +++++-- js/src/jspropertytree.cpp | 19 ++ js/src/jspropertytree.h | 1 + js/src/jsscope.h | 2 +- js/src/jstracer.cpp | 24 +- js/src/jstracer.h | 2 +- js/src/methodjit/MethodJIT.h | 2 +- js/src/methodjit/MonoIC.cpp | 12 +- js/src/methodjit/MonoIC.h | 2 +- js/src/xpconnect/src/xpcjsruntime.cpp | 5 +- 20 files changed, 470 insertions(+), 162 deletions(-) diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 844abc0a96ab..81102930b15f 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -885,20 +885,6 @@ DumpString(const nsAString &str) } #endif -static void -MaybeGC(JSContext *cx) -{ - size_t bytes = cx->runtime->gcBytes; - size_t lastBytes = cx->runtime->gcLastBytes; - if ((bytes > 8192 && bytes > lastBytes * 16) -#ifdef DEBUG - || cx->runtime->gcZeal > 0 -#endif - ) { - JS_GC(cx); - } -} - static already_AddRefed GetPromptFromContext(nsJSContext* ctx) { @@ -937,7 +923,7 @@ nsJSContext::DOMOperationCallback(JSContext *cx) PRTime callbackTime = ctx->mOperationCallbackTime; PRTime modalStateTime = ctx->mModalStateTime; - MaybeGC(cx); + JS_MaybeGC(cx); // Now restore the callback time and count, in case they got reset. ctx->mOperationCallbackTime = callbackTime; @@ -3541,12 +3527,12 @@ nsJSContext::ScriptEvaluated(PRBool aTerminated) #ifdef JS_GC_ZEAL if (mContext->runtime->gcZeal >= 2) { - MaybeGC(mContext); + JS_MaybeGC(mContext); } else #endif if (mNumEvaluations > 20) { mNumEvaluations = 0; - MaybeGC(mContext); + JS_MaybeGC(mContext); } if (aTerminated) { diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index e28da0e85b7e..5280e5bcfc53 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -2544,78 +2544,19 @@ JS_GC(JSContext *cx) /* Don't nuke active arenas if executing or compiling. */ if (cx->tempPool.current == &cx->tempPool.first) JS_FinishArenaPool(&cx->tempPool); - js_GC(cx, GC_NORMAL); + js_GC(cx, NULL, GC_NORMAL); } JS_PUBLIC_API(void) JS_MaybeGC(JSContext *cx) { - JSRuntime *rt; - uint32 bytes, lastBytes; + LeaveTrace(cx); - rt = cx->runtime; + /* Don't nuke active arenas if executing or compiling. */ + if (cx->tempPool.current == &cx->tempPool.first) + JS_FinishArenaPool(&cx->tempPool); -#ifdef JS_GC_ZEAL - if (rt->gcZeal > 0) { - JS_GC(cx); - return; - } -#endif - - bytes = rt->gcBytes; - lastBytes = rt->gcLastBytes; - - /* - * We run the GC if we used all available free GC cells and had to - * allocate extra 1/3 of GC arenas since the last run of GC, or if - * we have malloc'd more bytes through JS_malloc than we were told - * to allocate by JS_NewRuntime. - * - * The reason for - * bytes > 4/3 lastBytes - * condition is the following. Bug 312238 changed bytes and lastBytes - * to mean the total amount of memory that the GC uses now and right - * after the last GC. - * - * Before the bug the variables meant the size of allocated GC things - * now and right after the last GC. That size did not include the - * memory taken by free GC cells and the condition was - * bytes > 3/2 lastBytes. - * That is, we run the GC if we have half again as many bytes of - * GC-things as the last time we GC'd. To be compatible we need to - * express that condition through the new meaning of bytes and - * lastBytes. - * - * We write the original condition as - * B*(1-F) > 3/2 Bl*(1-Fl) - * where B is the total memory size allocated by GC and F is the free - * cell density currently and Sl and Fl are the size and the density - * right after GC. The density by definition is memory taken by free - * cells divided by total amount of memory. In other words, B and Bl - * are bytes and lastBytes with the new meaning and B*(1-F) and - * Bl*(1-Fl) are bytes and lastBytes with the original meaning. - * - * Our task is to exclude F and Fl from the last statement. According - * to the stats from bug 331966 comment 23, Fl is about 10-25% for a - * typical run of the browser. It means that the original condition - * implied that we did not run GC unless we exhausted the pool of - * free cells. Indeed if we still have free cells, then B == Bl since - * we did not yet allocated any new arenas and the condition means - * 1 - F > 3/2 (1-Fl) or 3/2Fl > 1/2 + F - * That implies 3/2 Fl > 1/2 or Fl > 1/3. That cannot be fulfilled - * for the state described by the stats. So we can write the original - * condition as: - * F == 0 && B > 3/2 Bl(1-Fl) - * Again using the stats we see that Fl is about 11% when the browser - * starts up and when we are far from hitting rt->gcMaxBytes. With - * this F we have - * F == 0 && B > 3/2 Bl(1-0.11) - * or approximately F == 0 && B > 4/3 Bl. - */ - if ((bytes > 8192 && bytes > lastBytes + lastBytes / 3) || - rt->isGCMallocLimitReached()) { - JS_GC(cx); - } + MaybeGC(cx); } JS_PUBLIC_API(JSGCCallback) @@ -2640,7 +2581,7 @@ JS_IsAboutToBeFinalized(JSContext *cx, void *thing) { JS_ASSERT(thing); JS_ASSERT(!cx->runtime->gcMarkingTracer); - return IsAboutToBeFinalized(thing); + return IsAboutToBeFinalized(cx, thing); } JS_PUBLIC_API(void) diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 21c493d89424..c8ed7c2e8e76 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -450,8 +450,8 @@ js_SweepAtomState(JSContext *cx) AtomEntryType entry = e.front(); if (AtomEntryFlags(entry) & (ATOM_PINNED | ATOM_INTERNED)) { /* Pinned or interned key cannot be finalized. */ - JS_ASSERT(!IsAboutToBeFinalized(AtomEntryToKey(entry))); - } else if (IsAboutToBeFinalized(AtomEntryToKey(entry))) { + JS_ASSERT(!IsAboutToBeFinalized(cx, AtomEntryToKey(entry))); + } else if (IsAboutToBeFinalized(cx, AtomEntryToKey(entry))) { e.removeFront(); } } diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 7fb6cbe389d3..24e7cebcd2f6 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -1072,7 +1072,7 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) #endif if (last) { - js_GC(cx, GC_LAST_CONTEXT); + js_GC(cx, NULL, GC_LAST_CONTEXT); DUMP_EVAL_CACHE_METER(cx); DUMP_FUNCTION_METER(cx); @@ -1082,7 +1082,7 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) JS_NOTIFY_ALL_CONDVAR(rt->stateChange); } else { if (mode == JSDCM_FORCE_GC) - js_GC(cx, GC_NORMAL); + js_GC(cx, NULL, GC_NORMAL); else if (mode == JSDCM_MAYBE_GC) JS_MaybeGC(cx); JS_LOCK_GC(rt); @@ -1821,7 +1821,7 @@ js_InvokeOperationCallback(JSContext *cx) JS_UNLOCK_GC(rt); if (rt->gcIsNeeded) { - js_GC(cx, GC_NORMAL); + js_GC(cx, rt->gcTriggerCompartment, GC_NORMAL); /* * On trace we can exceed the GC quota, see comments in NewGCArena. So diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index bed975c5ea77..69657112d3b5 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -1131,7 +1131,16 @@ struct JSRuntime { js::GCMarker *gcMarkingTracer; uint32 gcTriggerFactor; int64 gcJitReleaseTime; - volatile JSBool gcIsNeeded; + volatile bool gcIsNeeded; + + /* + * 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; /* * We can pack these flags as only the GC thread writes to them. Atomic diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index af21319445f0..20078120177c 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -57,6 +57,9 @@ using namespace js::gc; JSCompartment::JSCompartment(JSRuntime *rt) : rt(rt), principals(NULL), + gcBytes(0), + gcTriggerBytes(0), + gcLastBytes(0), data(NULL), marked(false), active(false), @@ -384,23 +387,30 @@ ScriptPoolDestroyed(JSContext *cx, mjit::JITScript *jit, } #endif +void +JSCompartment::mark(JSTracer *trc) +{ + for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) + MarkValue(trc, e.front().key, "cross-compartment wrapper"); +} + void JSCompartment::sweep(JSContext *cx, uint32 releaseInterval) { chunk = NULL; /* Remove dead wrappers from the table. */ for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { - JS_ASSERT_IF(IsAboutToBeFinalized(e.front().key.toGCThing()) && - !IsAboutToBeFinalized(e.front().value.toGCThing()), + JS_ASSERT_IF(IsAboutToBeFinalized(cx, e.front().key.toGCThing()) && + !IsAboutToBeFinalized(cx, e.front().value.toGCThing()), e.front().key.isString()); - if (IsAboutToBeFinalized(e.front().key.toGCThing()) || - IsAboutToBeFinalized(e.front().value.toGCThing())) { + if (IsAboutToBeFinalized(cx, e.front().key.toGCThing()) || + IsAboutToBeFinalized(cx, e.front().value.toGCThing())) { e.removeFront(); } } #ifdef JS_TRACER - traceMonitor.sweep(); + traceMonitor.sweep(cx); #endif #if defined JS_METHODJIT && defined JS_MONOIC @@ -418,7 +428,7 @@ JSCompartment::sweep(JSContext *cx, uint32 releaseInterval) for (JSCList *cursor = scripts.next; cursor != &scripts; cursor = cursor->next) { JSScript *script = reinterpret_cast(cursor); if (script->hasJITCode()) { - mjit::ic::SweepCallICs(script, discardScripts); + mjit::ic::SweepCallICs(cx, script, discardScripts); if (discardScripts) { if (script->jitNormal && ScriptPoolDestroyed(cx, script->jitNormal, releaseInterval, counter)) { diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 16fd0e8fbb2f..921af863953b 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -201,7 +201,7 @@ struct TraceMonitor { void flush(); /* Sweep any cache entry pointing to dead GC things. */ - void sweep(); + void sweep(JSContext *cx); bool outOfMemory() const; }; @@ -273,6 +273,10 @@ struct JS_FRIEND_API(JSCompartment) { js::gc::ArenaList arenas[js::gc::FINALIZE_LIMIT]; js::gc::FreeLists freeLists; + size_t gcBytes; + size_t gcTriggerBytes; + size_t gcLastBytes; + #ifdef JS_GCMETER js::gc::JSGCArenaStats compartmentStats[js::gc::FINALIZE_LIMIT]; #endif @@ -320,6 +324,7 @@ struct JS_FRIEND_API(JSCompartment) { bool init(); + void mark(JSTracer *trc); bool wrap(JSContext *cx, js::Value *vp); bool wrap(JSContext *cx, JSString **strp); bool wrap(JSContext *cx, JSObject **objp); @@ -331,8 +336,12 @@ struct JS_FRIEND_API(JSCompartment) { void sweep(JSContext *cx, uint32 releaseInterval); void purge(JSContext *cx); void finishArenaLists(); + void finalizeObjectArenaLists(JSContext *cx); + void finalizeStringArenaLists(JSContext *cx); bool arenaListsAreEmpty(); + void setGCLastBytes(size_t lastBytes); + private: js::MathCache *mathCache; diff --git a/js/src/jsdbgapi.cpp b/js/src/jsdbgapi.cpp index a0a4e36198ad..a0ec36aeb1f3 100644 --- a/js/src/jsdbgapi.cpp +++ b/js/src/jsdbgapi.cpp @@ -654,7 +654,7 @@ js_SweepWatchPoints(JSContext *cx) &wp->links != &rt->watchPointList; wp = next) { next = (JSWatchPoint *)wp->links.next; - if (IsAboutToBeFinalized(wp->object)) { + if (IsAboutToBeFinalized(cx, wp->object)) { sample = rt->debuggerMutations; /* Ignore failures. */ diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 760d601624fd..92a579faadb4 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -253,6 +253,15 @@ checkArenaListsForThing(JSCompartment *comp, void *thing) { return false; } + +bool +checkArenaListAllUnmarked(JSCompartment *comp) { + for (unsigned i = 0; i < FINALIZE_LIMIT; i++) { + if (comp->arenas[i].markedThingsInArenaList()) + return false; + } + return true; +} #endif } /* namespace gc */ @@ -323,8 +332,9 @@ Chunk::allocateArena(JSCompartment *comp, unsigned thingKind) JSRuntime *rt = info.runtime; rt->gcBytes += sizeof(Arena); - if (rt->gcBytes >= rt->gcTriggerBytes) - TriggerGC(rt); + comp->gcBytes += sizeof(Arena); + if (comp->gcBytes >= comp->gcTriggerBytes) + TriggerCompartmentGC(comp); METER(rt->gcStats.nallarenas++); return arena; } @@ -334,12 +344,15 @@ void Chunk::releaseArena(Arena *arena) { JSRuntime *rt = info.runtime; + JSCompartment *comp = arena->header()->compartment; METER(rt->gcStats.afree++); JS_ASSERT(rt->gcStats.nallarenas != 0); METER(rt->gcStats.nallarenas--); JS_ASSERT(rt->gcBytes >= sizeof(Arena)); + JS_ASSERT(comp->gcBytes >= sizeof(Arena)); rt->gcBytes -= sizeof(Arena); + comp->gcBytes -= sizeof(Arena); info.emptyArenaLists.insert((Arena *)arena); arena->header()->isUsed = false; ++info.numFree; @@ -476,10 +489,17 @@ AllocateArena(JSContext *cx, unsigned thingKind) } JS_FRIEND_API(bool) -IsAboutToBeFinalized(void *thing) +IsAboutToBeFinalized(JSContext *cx, void *thing) { if (JSString::isStatic(thing)) return false; + JS_ASSERT(cx); + + JSCompartment *thingCompartment = reinterpret_cast(thing)->compartment(); + JSRuntime *rt = cx->runtime; + + if (rt->gcCurrentCompartment != NULL && rt->gcCurrentCompartment != thingCompartment) + return false; return !reinterpret_cast(thing)->isMarked(); } @@ -540,6 +560,8 @@ js_InitGC(JSRuntime *rt, uint32 maxbytes) rt->gcTriggerFactor = uint32(100.0f * GC_HEAP_GROWTH_FACTOR); + rt->defaultCompartment->setGCLastBytes(8192); + /* * The assigned value prevents GC from running when GC memory is too low * (during JS engine start). @@ -1001,6 +1023,11 @@ JSRuntime::setGCTriggerFactor(uint32 factor) gcTriggerFactor = factor; setGCLastBytes(gcLastBytes); + + for (JSCompartment **c = compartments.begin(); c != compartments.end(); ++c) { + (*c)->setGCLastBytes(gcLastBytes); + } + defaultCompartment->setGCLastBytes(gcLastBytes); } void @@ -1016,6 +1043,19 @@ JSRuntime::setGCLastBytes(size_t lastBytes) gcTriggerBytes = (float(gcMaxBytes) < maxtriger) ? gcMaxBytes : size_t(maxtriger); } +void +JSCompartment::setGCLastBytes(size_t lastBytes) +{ + gcLastBytes = lastBytes; + + /* FIXME bug 603916 - we should unify the triggers here. */ + float trigger1 = float(lastBytes) * float(rt->gcTriggerFactor) / 100.0f; + float trigger2 = float(Max(lastBytes, GC_ARENA_ALLOCATION_TRIGGER)) * + GC_HEAP_GROWTH_FACTOR; + float maxtriger = Max(trigger1, trigger2); + gcTriggerBytes = (float(rt->gcMaxBytes) < maxtriger) ? rt->gcMaxBytes : size_t(maxtriger); +} + void FreeLists::purge() { @@ -1055,7 +1095,7 @@ NeedLastDitchGC(JSContext *cx) if (rt->gcZeal >= 1) return true; #endif - return !!rt->gcIsNeeded; + return rt->gcIsNeeded; } /* @@ -1074,7 +1114,7 @@ RunLastDitchGC(JSContext *cx) #endif /* The last ditch GC preserves all atoms. */ AutoKeepAtoms keep(rt); - js_GC(cx, GC_NORMAL); + js_GC(cx, rt->gcTriggerCompartment, GC_NORMAL); return rt->gcBytes < rt->gcMaxBytes; } @@ -1615,7 +1655,7 @@ MarkRuntime(JSTracer *trc) * builds for now, see bug 574313. */ JSContext *iter; -#if 1 +#if 0 iter = NULL; while (JSContext *acx = js_ContextIterator(rt, JS_TRUE, &iter)) { for (AutoGCRooter *gcr = acx->autoGCRooters; gcr; gcr = gcr->down) { @@ -1736,9 +1776,73 @@ TriggerGC(JSRuntime *rt) * thread. */ rt->gcIsNeeded = true; + rt->gcTriggerCompartment = NULL; TriggerAllOperationCallbacks(rt); } +void +TriggerCompartmentGC(JSCompartment *comp) +{ + JSRuntime *rt = comp->rt; + JS_ASSERT(!rt->gcRunning); + +#ifdef JS_GC_ZEAL + if (rt->gcZeal >= 1) { + TriggerGC(rt); + return; + } +#endif + + if (comp == rt->defaultCompartment) { + /* We can't do a compartmental GC of the default compartment. */ + TriggerGC(rt); + return; + } + + if (rt->gcIsNeeded) { + /* If we need to GC more than one compartment, run a full GC. */ + if (rt->gcTriggerCompartment != comp) + rt->gcTriggerCompartment = NULL; + return; + } + + if (rt->gcBytes > 8192 && rt->gcBytes >= 3 * (rt->gcTriggerBytes / 2)) { + /* If we're using significantly more than our quota, do a full GC. */ + TriggerGC(rt); + return; + } + + /* + * Trigger the GC when it is safe to call an operation callback on any + * thread. + */ + rt->gcIsNeeded = true; + rt->gcTriggerCompartment = comp; + TriggerAllOperationCallbacks(comp->rt); +} + +void +MaybeGC(JSContext *cx) +{ + JSRuntime *rt = cx->runtime; + +#ifdef JS_GC_ZEAL + if (rt->gcZeal > 0) { + js_GC(cx, NULL, GC_NORMAL); + return; + } +#endif + + JSCompartment *comp = cx->compartment; + if (rt->gcIsNeeded) { + js_GC(cx, comp == rt->gcTriggerCompartment ? comp : NULL, GC_NORMAL); + return; + } + + if (comp->gcBytes > 8192 && comp->gcBytes >= 3 * (comp->gcTriggerBytes / 4)) + js_GC(cx, comp, GC_NORMAL); +} + } /* namespace js */ void @@ -1902,6 +2006,29 @@ FinalizeArenaList(JSCompartment *comp, JSContext *cx, unsigned thingKind) METER(UpdateCompartmentStats(comp, thingKind, nlivearenas, nkilledarenas, nthings)); } +void +JSCompartment::finalizeObjectArenaLists(JSContext *cx) +{ + FinalizeArenaList(this, cx, FINALIZE_OBJECT0); + FinalizeArenaList(this, cx, FINALIZE_OBJECT2); + FinalizeArenaList(this, cx, FINALIZE_OBJECT4); + FinalizeArenaList(this, cx, FINALIZE_OBJECT8); + FinalizeArenaList(this, cx, FINALIZE_OBJECT12); + FinalizeArenaList(this, cx, FINALIZE_OBJECT16); + FinalizeArenaList(this, cx, FINALIZE_FUNCTION); +#if JS_HAS_XML_SUPPORT + FinalizeArenaList(this, cx, FINALIZE_XML); +#endif +} + +void +JSCompartment::finalizeStringArenaLists(JSContext *cx) +{ + FinalizeArenaList(this, cx, FINALIZE_SHORT_STRING); + FinalizeArenaList(this, cx, FINALIZE_STRING); + FinalizeArenaList(this, cx, FINALIZE_EXTERNAL_STRING); +} + #ifdef JS_THREADSAFE namespace js { @@ -2091,7 +2218,8 @@ PreGCCleanup(JSContext *cx, JSGCInvocationKind gckind) JSRuntime *rt = cx->runtime; /* Clear gcIsNeeded now, when we are about to start a normal GC cycle. */ - rt->gcIsNeeded = JS_FALSE; + rt->gcIsNeeded = false; + rt->gcTriggerCompartment = NULL; /* Reset malloc counter. */ rt->resetGCMallocBytes(); @@ -2117,8 +2245,13 @@ PreGCCleanup(JSContext *cx, JSGCInvocationKind gckind) rt->shapeGen = Shape::LAST_RESERVED_SHAPE; rt->protoHazardShape = 0; } - for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) - (*c)->purge(cx); + + if (rt->gcCurrentCompartment) { + rt->gcCurrentCompartment->purge(cx); + } else { + for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) + (*c)->purge(cx); + } js_PurgeThreads(cx); { @@ -2128,6 +2261,116 @@ PreGCCleanup(JSContext *cx, JSGCInvocationKind gckind) } } +static void +MarkAndSweepCompartment(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind GCTIMER_PARAM) +{ + JSRuntime *rt = cx->runtime; + rt->gcNumber++; + JS_ASSERT(!rt->gcRegenShapes); + JS_ASSERT(gckind != GC_LAST_CONTEXT); + + /* + * Mark phase. + */ + GCMarker gcmarker(cx); + JS_ASSERT(IS_GC_MARKING_TRACER(&gcmarker)); + JS_ASSERT(gcmarker.getMarkColor() == BLACK); + rt->gcMarkingTracer = &gcmarker; + gcmarker.stackLimit = cx->stackLimit; + + for (GCChunkSet::Range r(rt->gcChunkSet.all()); !r.empty(); r.popFront()) + r.front()->clearMarkBitmap(); + + for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) + (*c)->mark(&gcmarker); + + MarkRuntime(&gcmarker); + + /* + * Mark children of things that caused too deep recursion during the above + * tracing. + */ + gcmarker.markDelayedChildren(); + + rt->gcMarkingTracer = NULL; + + if (rt->gcCallback) + (void) rt->gcCallback(cx, JSGC_MARK_END); + +#ifdef JS_THREADSAFE + /* + * cx->gcBackgroundFree is set if we need several mark-and-sweep loops to + * finish the GC. + */ + if(!cx->gcBackgroundFree) { + /* Wait until the sweeping from the previois GC finishes. */ + rt->gcHelperThread.waitBackgroundSweepEnd(rt); + cx->gcBackgroundFree = &rt->gcHelperThread; + } +#endif +#ifdef DEBUG + /* Make sure that we didn't mark an object in another compartment */ + for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) + JS_ASSERT_IF(*c != comp, checkArenaListAllUnmarked(*c)); +#endif + + /* + * Sweep phase. + * + * Finalize as we sweep, outside of rt->gcLock but with rt->gcRunning set + * so that any attempt to allocate a GC-thing from a finalizer will fail, + * rather than nest badly and leave the unmarked newborn to be swept. + * + * We first sweep atom state so we can use js_IsAboutToBeFinalized on + * JSString held in a hashtable to check if the hashtable entry can be + * freed. Note that even after the entry is freed, JSObject finalizers can + * continue to access the corresponding JSString* assuming that they are + * unique. This works since the atomization API must not be called during + * the GC. + */ + TIMESTAMP(startSweep); + js_SweepAtomState(cx); + + /* Finalize watch points associated with unreachable objects. */ + js_SweepWatchPoints(cx); + +#ifdef DEBUG + /* Save the pre-sweep count of scope-mapped properties. */ + rt->liveObjectPropsPreSweep = rt->liveObjectProps; +#endif + + /* + * We finalize iterators before other objects so the iterator can use the + * object which properties it enumerates over to finalize the enumeration + * state. We finalize objects before other GC things to ensure that + * object's finalizer can access them even if they will be freed. + */ + + comp->finalizeObjectArenaLists(cx); + TIMESTAMP(sweepObjectEnd); + + comp->finalizeStringArenaLists(cx); + TIMESTAMP(sweepStringEnd); + + comp->sweep(cx, 0); + + /* + * Unmark the runtime's property trees because we don't + * sweep them. + */ + js::PropertyTree::unmarkShapes(cx); + + /* + * Destroy arenas after we finished the sweeping so finalizers can safely + * use js_IsAboutToBeFinalized(). + */ + ExpireGCChunks(rt); + TIMESTAMP(sweepDestroyEnd); + + if (rt->gcCallback) + (void) rt->gcCallback(cx, JSGC_FINALIZE_END); +} + /* * Perform mark-and-sweep GC. * @@ -2211,25 +2454,13 @@ MarkAndSweep(JSContext *cx, JSGCInvocationKind gckind GCTIMER_PARAM) * object's finalizer can access them even if they will be freed. */ - for (JSCompartment **comp = rt->compartments.begin(); comp != rt->compartments.end(); comp++) { - FinalizeArenaList(*comp, cx, FINALIZE_OBJECT0); - FinalizeArenaList(*comp, cx, FINALIZE_OBJECT2); - FinalizeArenaList(*comp, cx, FINALIZE_OBJECT4); - FinalizeArenaList(*comp, cx, FINALIZE_OBJECT8); - FinalizeArenaList(*comp, cx, FINALIZE_OBJECT12); - FinalizeArenaList(*comp, cx, FINALIZE_OBJECT16); - FinalizeArenaList(*comp, cx, FINALIZE_FUNCTION); -#if JS_HAS_XML_SUPPORT - FinalizeArenaList(*comp, cx, FINALIZE_XML); -#endif - } + for (JSCompartment **comp = rt->compartments.begin(); comp != rt->compartments.end(); comp++) + (*comp)->finalizeObjectArenaLists(cx); + TIMESTAMP(sweepObjectEnd); - for (JSCompartment **comp = rt->compartments.begin(); comp != rt->compartments.end(); comp++) { - FinalizeArenaList(*comp, cx, FINALIZE_SHORT_STRING); - FinalizeArenaList(*comp, cx, FINALIZE_STRING); - FinalizeArenaList(*comp, cx, FINALIZE_EXTERNAL_STRING); - } + for (JSCompartment **comp = rt->compartments.begin(); comp != rt->compartments.end(); comp++) + (*comp)->finalizeStringArenaLists(cx); TIMESTAMP(sweepStringEnd); @@ -2444,7 +2675,7 @@ AutoGCSession::~AutoGCSession() * garbage and no other threads are demanding more GC. */ static void -GCUntilDone(JSContext *cx, JSGCInvocationKind gckind GCTIMER_PARAM) +GCUntilDone(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind GCTIMER_PARAM) { if (JS_ON_TRACE(cx)) return; @@ -2466,6 +2697,9 @@ GCUntilDone(JSContext *cx, JSGCInvocationKind gckind GCTIMER_PARAM) AutoGCSession gcsession(cx); + JS_ASSERT(!rt->gcCurrentCompartment); + rt->gcCurrentCompartment = comp; + METER(rt->gcStats.poke++); bool firstRun = true; @@ -2482,7 +2716,11 @@ GCUntilDone(JSContext *cx, JSGCInvocationKind gckind GCTIMER_PARAM) TIMESTAMP(startMark); firstRun = false; } - MarkAndSweep(cx, gckind GCTIMER_ARG); + + if (comp) + MarkAndSweepCompartment(cx, comp, gckind GCTIMER_ARG); + else + MarkAndSweep(cx, gckind GCTIMER_ARG); // GC again if: // - another thread, not in a request, called js_GC @@ -2499,10 +2737,16 @@ GCUntilDone(JSContext *cx, JSGCInvocationKind gckind GCTIMER_PARAM) rt->gcMarkAndSweep = false; rt->gcRegenShapes = false; rt->setGCLastBytes(rt->gcBytes); + rt->gcCurrentCompartment = NULL; + + for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) { + (*c)->setGCLastBytes((*c)->gcBytes); + (*c)->marked = false; + } } void -js_GC(JSContext *cx, JSGCInvocationKind gckind) +js_GC(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind) { JSRuntime *rt = cx->runtime; @@ -2547,7 +2791,7 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) /* Lock out other GC allocator and collector invocations. */ AutoLockGC lock(rt); - GCUntilDone(cx, gckind GCTIMER_ARG); + GCUntilDone(cx, comp, gckind GCTIMER_ARG); } /* We re-sample the callback again as the finalizers can change it. */ @@ -2621,6 +2865,8 @@ NewCompartment(JSContext *cx, JSPrincipals *principals) JSPRINCIPALS_HOLD(cx, principals); } + compartment->setGCLastBytes(8192); + { AutoLockGC lock(rt); diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 59c9eb604376..a013ea18a51e 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -70,6 +70,9 @@ js_TraceXML(JSTracer *trc, JSXML* thing); #endif namespace js { + +struct Shape; + namespace gc { /* @@ -201,6 +204,16 @@ struct ArenaBitmap { } return true; } + +#ifdef DEBUG + bool noBitsSet() { + for (unsigned i = 0; i < BitWords; i++) { + if (bitmap[i] != uintptr_t(0)) + return false; + } + return true; + } +#endif }; /* Ensure that bitmap covers the whole arena. */ @@ -574,6 +587,14 @@ struct ArenaList { } return false; } + + bool markedThingsInArenaList() { + for (Arena *a = (Arena *) head; a; a = (Arena *) a->header()->next) { + if (!a->bitmap()->noBitsSet()) + return true; + } + return false; + } #endif inline void insert(Arena *a) { @@ -768,7 +789,7 @@ extern void js_UnlockGCThingRT(JSRuntime *rt, void *thing); extern JS_FRIEND_API(bool) -IsAboutToBeFinalized(void *thing); +IsAboutToBeFinalized(JSContext *cx, void *thing); extern JS_FRIEND_API(bool) js_GCThingIsMarked(void *thing, uint32 color); @@ -791,6 +812,13 @@ MarkContext(JSTracer *trc, JSContext *acx); extern void TriggerGC(JSRuntime *rt); +/* Must be called with GC lock taken. */ +extern void +TriggerCompartmentGC(JSCompartment *comp); + +extern void +MaybeGC(JSContext *cx); + } /* namespace js */ /* @@ -807,8 +835,9 @@ typedef enum JSGCInvocationKind { GC_LAST_CONTEXT = 1 } JSGCInvocationKind; +/* Pass NULL for |comp| to get a full GC. */ extern void -js_GC(JSContext *cx, JSGCInvocationKind gckind); +js_GC(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind); #ifdef JS_THREADSAFE /* diff --git a/js/src/jsgcinlines.h b/js/src/jsgcinlines.h index ddbd72b8fd30..30e479906735 100644 --- a/js/src/jsgcinlines.h +++ b/js/src/jsgcinlines.h @@ -190,6 +190,14 @@ Mark(JSTracer *trc, T *thing) JS_ASSERT(JS_IS_VALID_TRACE_KIND(GetGCThingTraceKind(thing))); JS_ASSERT(trc->debugPrinter || trc->debugPrintArg); + /* Per-Compartment GC only with GCMarker and no custom JSTracer */ + JS_ASSERT_IF(trc->context->runtime->gcCurrentCompartment, IS_GC_MARKING_TRACER(trc)); + + JSRuntime *rt = trc->context->runtime; + /* Don't mark things outside a compartment if we are in a per-compartment GC */ + if (rt->gcCurrentCompartment && thing->asCell()->compartment() != rt->gcCurrentCompartment) + goto out; + if (!IS_GC_MARKING_TRACER(trc)) { uint32 kind = GetGCThingTraceKind(thing); trc->callback(trc, thing, kind); @@ -197,7 +205,7 @@ Mark(JSTracer *trc, T *thing) } TypedMarker(trc, thing); - + out: #ifdef DEBUG trc->debugPrinter = NULL; @@ -330,7 +338,7 @@ TypedMarker(JSTracer *trc, JSFunction *thing) { JS_ASSERT(thing); JS_ASSERT(JSTRACE_OBJECT == GetFinalizableTraceKind(thing->asCell()->arena()->header()->thingKind)); - + GCMarker *gcmarker = static_cast(trc); if (!thing->markIfUnmarked(gcmarker->getMarkColor())) return; @@ -378,14 +386,36 @@ Untag(JSString *str) } static JS_ALWAYS_INLINE void -NonRopeTypedMarker(JSString *str) +NonRopeTypedMarker(JSRuntime *rt, JSString *str) { /* N.B. The base of a dependent string is not necessarily flat. */ JS_ASSERT(!str->isRope()); - while (!JSString::isStatic(str) && - str->asCell()->markIfUnmarked() && - str->isDependent()) { - str = str->dependentBase(); + + if (rt->gcCurrentCompartment) { + for (;;) { + if (JSString::isStatic(str)) + break; + + /* + * If we perform single-compartment GC don't mark Strings outside the current compartment. + * Dependent Strings are not shared between compartments and they can't be in the defaultCompartment. + */ + if (str->asCell()->compartment() != rt->gcCurrentCompartment) { + JS_ASSERT(str->asCell()->compartment() == rt->defaultCompartment); + break; + } + if (!str->asCell()->markIfUnmarked()) + break; + if (!str->isDependent()) + break; + str = str->dependentBase(); + } + } else { + while (!JSString::isStatic(str) && + str->asCell()->markIfUnmarked() && + str->isDependent()) { + str = str->dependentBase(); + } } } @@ -397,9 +427,13 @@ static JS_ALWAYS_INLINE void TypedMarker(JSTracer *trc, JSString *str) { using namespace detail; - + JSRuntime *rt = trc->context->runtime; + JS_ASSERT(!JSString::isStatic(str)); +#ifdef DEBUG + JSCompartment *strComp = str->asCell()->compartment(); +#endif if (!str->isRope()) { - NonRopeTypedMarker(str); + NonRopeTypedMarker(rt, str); return; } @@ -411,6 +445,8 @@ TypedMarker(JSTracer *trc, JSString *str) */ JSString *parent = NULL; first_visit_node: { + JS_ASSERT(strComp == str->asCell()->compartment() || str->asCell()->compartment() == rt->defaultCompartment); + JS_ASSERT(!JSString::isStatic(str)); if (!str->asCell()->markIfUnmarked()) goto finish_node; JSString *left = str->ropeLeft(); @@ -421,7 +457,10 @@ TypedMarker(JSTracer *trc, JSString *str) str = left; goto first_visit_node; } - NonRopeTypedMarker(left); + JS_ASSERT_IF(!JSString::isStatic(left), + strComp == left->asCell()->compartment() + || left->asCell()->compartment() == rt->defaultCompartment); + NonRopeTypedMarker(rt, left); } visit_right_child: { JSString *right = str->ropeRight(); @@ -432,7 +471,10 @@ TypedMarker(JSTracer *trc, JSString *str) str = right; goto first_visit_node; } - NonRopeTypedMarker(right); + JS_ASSERT_IF(!JSString::isStatic(right), + strComp == right->asCell()->compartment() + || right->asCell()->compartment() == rt->defaultCompartment); + NonRopeTypedMarker(rt, right); } finish_node: { if (!parent) @@ -460,7 +502,9 @@ MarkAtomRange(JSTracer *trc, size_t len, JSAtom **vec, const char *name) for (uint32 i = 0; i < len; i++) { if (JSAtom *atom = vec[i]) { JS_SET_TRACING_INDEX(trc, name, i); - Mark(trc, ATOM_TO_STRING(atom)); + JSString *str = ATOM_TO_STRING(atom); + if (!JSString::isStatic(str)) + Mark(trc, str); } } } @@ -479,8 +523,11 @@ MarkObjectRange(JSTracer *trc, size_t len, JSObject **vec, const char *name) static inline void MarkId(JSTracer *trc, jsid id) { - if (JSID_IS_STRING(id)) - Mark(trc, JSID_TO_STRING(id)); + if (JSID_IS_STRING(id)) { + JSString *str = JSID_TO_STRING(id); + if (!JSString::isStatic(str)) + Mark(trc, str); + } else if (JS_UNLIKELY(JSID_IS_OBJECT(id))) Mark(trc, JSID_TO_OBJECT(id)); } diff --git a/js/src/jspropertytree.cpp b/js/src/jspropertytree.cpp index 7c93ed8b86c3..fc411f555eb4 100644 --- a/js/src/jspropertytree.cpp +++ b/js/src/jspropertytree.cpp @@ -895,3 +895,22 @@ js::PropertyTree::sweepShapes(JSContext *cx) } #endif /* DEBUG */ } + +void +js::PropertyTree::unmarkShapes(JSContext *cx) +{ + JSArena **ap = &JS_PROPERTY_TREE(cx).arenaPool.first.next; + while (JSArena *a = *ap) { + Shape *limit = (Shape *) a->avail; + + for (Shape *shape = (Shape *) a->base; shape < limit; shape++) { + /* If the id is null, shape is already on the freelist. */ + if (JSID_IS_VOID(shape->id)) + continue; + + if (shape->marked()) + shape->clearMark(); + } + ap = &a->next; + } +} diff --git a/js/src/jspropertytree.h b/js/src/jspropertytree.h index d5dad3ad6f29..31c94faac600 100644 --- a/js/src/jspropertytree.h +++ b/js/src/jspropertytree.h @@ -144,6 +144,7 @@ class PropertyTree static void orphanKids(JSContext *cx, js::Shape *shape); static void sweepShapes(JSContext *cx); + static void unmarkShapes(JSContext *cx); #ifdef DEBUG static void meter(JSBasicStats *bs, js::Shape *node); #endif diff --git a/js/src/jsscope.h b/js/src/jsscope.h index ebed7da7cbaf..c1a820df4bd0 100644 --- a/js/src/jsscope.h +++ b/js/src/jsscope.h @@ -296,7 +296,7 @@ struct Shape : public JSObjectMap friend struct ::JSFunction; friend class js::PropertyTree; friend class js::Bindings; - friend bool HasUnreachableGCThings(TreeFragment *f); + friend bool IsShapeAboutToBeFinalized(JSContext *cx, const js::Shape *shape); protected: mutable uint32 numSearches; /* Only updated until it reaches HASH_MIN_SEARCHES. */ diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index e3c0f8cbe497..a3a89c86517f 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -2771,33 +2771,43 @@ TraceMonitor::flush() } inline bool -HasUnreachableGCThings(TreeFragment *f) +IsShapeAboutToBeFinalized(JSContext *cx, const js::Shape *shape) +{ + JSRuntime *rt = cx->runtime; + if (rt->gcCurrentCompartment != NULL) + return false; + + return !shape->marked(); +} + +inline bool +HasUnreachableGCThings(JSContext *cx, TreeFragment *f) { /* * We do not check here for dead scripts as JSScript is not a GC thing. * Instead PurgeScriptFragments is used to remove dead script fragments. * See bug 584860. */ - if (IsAboutToBeFinalized(f->globalObj)) + if (IsAboutToBeFinalized(cx, f->globalObj)) return true; Value* vp = f->gcthings.data(); for (unsigned len = f->gcthings.length(); len; --len) { Value &v = *vp++; JS_ASSERT(v.isMarkable()); - if (IsAboutToBeFinalized(v.toGCThing())) + if (IsAboutToBeFinalized(cx, v.toGCThing())) return true; } const Shape** shapep = f->shapes.data(); for (unsigned len = f->shapes.length(); len; --len) { const Shape* shape = *shapep++; - if (!shape->marked()) + if (IsShapeAboutToBeFinalized(cx, shape)) return true; } return false; } void -TraceMonitor::sweep() +TraceMonitor::sweep(JSContext *cx) { JS_ASSERT(!ontrace()); debug_only_print0(LC_TMTracer, "Purging fragments with dead things"); @@ -2806,7 +2816,7 @@ TraceMonitor::sweep() TreeFragment *recorderTree = NULL; if (recorder) { recorderTree = recorder->getTree(); - shouldAbortRecording = HasUnreachableGCThings(recorderTree); + shouldAbortRecording = HasUnreachableGCThings(cx, recorderTree); } for (size_t i = 0; i < FRAGMENT_TABLE_SIZE; ++i) { @@ -2814,7 +2824,7 @@ TraceMonitor::sweep() while (TreeFragment* frag = *fragp) { TreeFragment* peer = frag; do { - if (HasUnreachableGCThings(peer)) + if (HasUnreachableGCThings(cx, peer)) break; peer = peer->peer; } while (peer); diff --git a/js/src/jstracer.h b/js/src/jstracer.h index 087e717efba6..40f1b7352abc 100644 --- a/js/src/jstracer.h +++ b/js/src/jstracer.h @@ -1580,7 +1580,7 @@ class TraceRecorder bool *blacklist); friend AbortResult AbortRecording(JSContext*, const char*); friend class BoxArg; - friend void TraceMonitor::sweep(); + friend void TraceMonitor::sweep(JSContext *cx); public: static bool JS_REQUIRES_STACK diff --git a/js/src/methodjit/MethodJIT.h b/js/src/methodjit/MethodJIT.h index eed4a36256f6..6b74ecb6cced 100644 --- a/js/src/methodjit/MethodJIT.h +++ b/js/src/methodjit/MethodJIT.h @@ -351,7 +351,7 @@ struct JITScript { } void nukeScriptDependentICs(); - void sweepCallICs(bool purgeAll); + void sweepCallICs(JSContext *cx, bool purgeAll); void purgeMICs(); void purgePICs(); diff --git a/js/src/methodjit/MonoIC.cpp b/js/src/methodjit/MonoIC.cpp index 71a020331310..fd7fd41a1473 100644 --- a/js/src/methodjit/MonoIC.cpp +++ b/js/src/methodjit/MonoIC.cpp @@ -1118,7 +1118,7 @@ JITScript::nukeScriptDependentICs() } void -JITScript::sweepCallICs(bool purgeAll) +JITScript::sweepCallICs(JSContext *cx, bool purgeAll) { Repatcher repatcher(this); @@ -1137,9 +1137,9 @@ JITScript::sweepCallICs(bool purgeAll) * precisely GC call ICs while keeping the identity guard safe. */ bool fastFunDead = ic.fastGuardedObject && - (purgeAll || IsAboutToBeFinalized(ic.fastGuardedObject)); + (purgeAll || IsAboutToBeFinalized(cx, ic.fastGuardedObject)); bool nativeDead = ic.fastGuardedNative && - (purgeAll || IsAboutToBeFinalized(ic.fastGuardedNative)); + (purgeAll || IsAboutToBeFinalized(cx, ic.fastGuardedNative)); if (fastFunDead) { repatcher.repatch(ic.funGuard, NULL); @@ -1189,12 +1189,12 @@ JITScript::sweepCallICs(bool purgeAll) } void -ic::SweepCallICs(JSScript *script, bool purgeAll) +ic::SweepCallICs(JSContext *cx, JSScript *script, bool purgeAll) { if (script->jitNormal) - script->jitNormal->sweepCallICs(purgeAll); + script->jitNormal->sweepCallICs(cx, purgeAll); if (script->jitCtor) - script->jitCtor->sweepCallICs(purgeAll); + script->jitCtor->sweepCallICs(cx, purgeAll); } #endif /* JS_MONOIC */ diff --git a/js/src/methodjit/MonoIC.h b/js/src/methodjit/MonoIC.h index f99cf43aee0e..0af48e0fb65b 100644 --- a/js/src/methodjit/MonoIC.h +++ b/js/src/methodjit/MonoIC.h @@ -266,7 +266,7 @@ void JS_FASTCALL NativeCall(VMFrame &f, ic::CallICInfo *ic); JSBool JS_FASTCALL SplatApplyArgs(VMFrame &f); void PurgeMICs(JSContext *cx, JSScript *script); -void SweepCallICs(JSScript *script, bool purgeAll); +void SweepCallICs(JSContext *cx, JSScript *script, bool purgeAll); } /* namespace ic */ } /* namespace mjit */ diff --git a/js/src/xpconnect/src/xpcjsruntime.cpp b/js/src/xpconnect/src/xpcjsruntime.cpp index 41799e87198d..262d8ad17804 100644 --- a/js/src/xpconnect/src/xpcjsruntime.cpp +++ b/js/src/xpconnect/src/xpcjsruntime.cpp @@ -555,9 +555,10 @@ static JSDHashOperator SweepWaiverWrappers(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number, void *arg) { + JSContext *cx = (JSContext *)arg; JSObject *key = ((JSObject2JSObjectMap::Entry *)hdr)->key; JSObject *value = ((JSObject2JSObjectMap::Entry *)hdr)->value; - if(IsAboutToBeFinalized(key) || IsAboutToBeFinalized(value)) + if(IsAboutToBeFinalized(cx, key) || IsAboutToBeFinalized(cx, value)) return JS_DHASH_REMOVE; return JS_DHASH_NEXT; } @@ -568,7 +569,7 @@ SweepCompartment(nsCStringHashKey& aKey, JSCompartment *compartment, void *aClos xpc::CompartmentPrivate *priv = (xpc::CompartmentPrivate *) JS_GetCompartmentPrivate((JSContext *)aClosure, compartment); if (priv->waiverWrapperMap) - priv->waiverWrapperMap->Enumerate(SweepWaiverWrappers, nsnull); + priv->waiverWrapperMap->Enumerate(SweepWaiverWrappers, (JSContext *)aClosure); return PL_DHASH_NEXT; }