mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-06 09:05:45 +00:00
Bug 966646 - Use JS helper threads for GC background sweeping / allocation, r=billm.
This commit is contained in:
parent
1e350332ac
commit
a815c1a492
@ -144,18 +144,30 @@ class GCRuntime
|
||||
#ifdef JS_THREADSAFE
|
||||
void notifyRequestEnd() { conservativeGC.updateForRequestEnd(); }
|
||||
#endif
|
||||
bool isBackgroundSweeping() { return helperThread.sweeping(); }
|
||||
void waitBackgroundSweepEnd() { helperThread.waitBackgroundSweepEnd(); }
|
||||
void waitBackgroundSweepOrAllocEnd() { helperThread.waitBackgroundSweepOrAllocEnd(); }
|
||||
void startBackgroundShrink() { helperThread.startBackgroundShrink(); }
|
||||
void freeLater(void *p) { helperThread.freeLater(p); }
|
||||
bool isBackgroundSweeping() { return helperState.isBackgroundSweeping(); }
|
||||
void waitBackgroundSweepEnd() { helperState.waitBackgroundSweepEnd(); }
|
||||
void waitBackgroundSweepOrAllocEnd() { helperState.waitBackgroundSweepOrAllocEnd(); }
|
||||
void startBackgroundShrink() { helperState.startBackgroundShrink(); }
|
||||
void startBackgroundAllocationIfIdle() { helperState.startBackgroundAllocationIfIdle(); }
|
||||
void freeLater(void *p) { helperState.freeLater(p); }
|
||||
|
||||
#ifdef DEBUG
|
||||
bool onBackgroundThread() { return helperThread.onBackgroundThread(); }
|
||||
|
||||
bool onBackgroundThread() { return helperState.onBackgroundThread(); }
|
||||
|
||||
bool currentThreadOwnsGCLock() {
|
||||
#ifdef JS_THREADSAFE
|
||||
return lockOwner == PR_GetCurrentThread();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // DEBUG
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
void assertCanLock() {
|
||||
JS_ASSERT(lockOwner != PR_GetCurrentThread());
|
||||
JS_ASSERT(!currentThreadOwnsGCLock());
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -203,7 +215,7 @@ class GCRuntime
|
||||
private:
|
||||
// For ArenaLists::allocateFromArenaInline()
|
||||
friend class ArenaLists;
|
||||
Chunk *pickChunk(Zone *zone);
|
||||
Chunk *pickChunk(Zone *zone, AutoMaybeStartBackgroundAllocation &maybeStartBackgroundAllocation);
|
||||
|
||||
inline bool wantBackgroundAllocation() const;
|
||||
|
||||
@ -533,16 +545,16 @@ class GCRuntime
|
||||
size_t noGCOrAllocationCheck;
|
||||
#endif
|
||||
|
||||
/* Synchronize GC heap access between main thread and GCHelperThread. */
|
||||
/* Synchronize GC heap access between main thread and GCHelperState. */
|
||||
PRLock *lock;
|
||||
mozilla::DebugOnly<PRThread *> lockOwner;
|
||||
|
||||
js::GCHelperThread helperThread;
|
||||
GCHelperState helperState;
|
||||
|
||||
ConservativeGCData conservativeGC;
|
||||
|
||||
//friend class js::gc::Chunk; // todo: remove
|
||||
friend class js::GCHelperThread;
|
||||
friend class js::GCHelperState;
|
||||
friend class js::gc::MarkingValidator;
|
||||
};
|
||||
|
||||
|
321
js/src/jsgc.cpp
321
js/src/jsgc.cpp
@ -960,14 +960,40 @@ GCRuntime::wantBackgroundAllocation() const
|
||||
* allocation if we have empty chunks or when the runtime needs just few
|
||||
* of them.
|
||||
*/
|
||||
return helperThread.canBackgroundAllocate() &&
|
||||
return helperState.canBackgroundAllocate() &&
|
||||
chunkPool.getEmptyCount() == 0 &&
|
||||
chunkSet.count() >= 4;
|
||||
}
|
||||
|
||||
class js::gc::AutoMaybeStartBackgroundAllocation
|
||||
{
|
||||
private:
|
||||
JSRuntime *runtime;
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
|
||||
public:
|
||||
AutoMaybeStartBackgroundAllocation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
|
||||
: runtime(nullptr)
|
||||
{
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
}
|
||||
|
||||
void tryToStartBackgroundAllocation(JSRuntime *rt) {
|
||||
runtime = rt;
|
||||
}
|
||||
|
||||
~AutoMaybeStartBackgroundAllocation() {
|
||||
if (runtime && !runtime->currentThreadOwnsInterruptLock()) {
|
||||
AutoLockWorkerThreadState workerLock;
|
||||
AutoLockGC lock(runtime);
|
||||
runtime->gc.startBackgroundAllocationIfIdle();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* The caller must hold the GC lock. */
|
||||
Chunk *
|
||||
GCRuntime::pickChunk(Zone *zone)
|
||||
GCRuntime::pickChunk(Zone *zone, AutoMaybeStartBackgroundAllocation &maybeStartBackgroundAllocation)
|
||||
{
|
||||
Chunk **listHeadp = GetAvailableChunkList(zone);
|
||||
Chunk *chunk = *listHeadp;
|
||||
@ -986,7 +1012,7 @@ GCRuntime::pickChunk(Zone *zone)
|
||||
JS_ASSERT(!chunkSet.has(chunk));
|
||||
|
||||
if (wantBackgroundAllocation())
|
||||
helperThread.startBackgroundAllocationIfIdle();
|
||||
maybeStartBackgroundAllocation.tryToStartBackgroundAllocation(rt);
|
||||
|
||||
chunkAllocationSinceLastGC = true;
|
||||
|
||||
@ -1094,7 +1120,7 @@ GCRuntime::GCRuntime(JSRuntime *rt) :
|
||||
#endif
|
||||
lock(nullptr),
|
||||
lockOwner(nullptr),
|
||||
helperThread(rt)
|
||||
helperState(rt)
|
||||
{
|
||||
}
|
||||
|
||||
@ -1191,7 +1217,7 @@ GCRuntime::init(uint32_t maxbytes)
|
||||
if (!rootsHash.init(256))
|
||||
return false;
|
||||
|
||||
if (!helperThread.init())
|
||||
if (!helperState.init())
|
||||
return false;
|
||||
|
||||
/*
|
||||
@ -1242,7 +1268,7 @@ GCRuntime::finish()
|
||||
* Wait until the background finalization stops and the helper thread
|
||||
* shuts down before we forcefully release any remaining GC memory.
|
||||
*/
|
||||
helperThread.finish();
|
||||
helperState.finish();
|
||||
|
||||
#ifdef JS_GC_ZEAL
|
||||
/* Free memory associated with GC verification. */
|
||||
@ -1502,7 +1528,8 @@ PushArenaAllocatedDuringSweep(JSRuntime *runtime, ArenaHeader *arena)
|
||||
}
|
||||
|
||||
inline void *
|
||||
ArenaLists::allocateFromArenaInline(Zone *zone, AllocKind thingKind)
|
||||
ArenaLists::allocateFromArenaInline(Zone *zone, AllocKind thingKind,
|
||||
AutoMaybeStartBackgroundAllocation &maybeStartBackgroundAllocation)
|
||||
{
|
||||
/*
|
||||
* Parallel JS Note:
|
||||
@ -1575,7 +1602,7 @@ ArenaLists::allocateFromArenaInline(Zone *zone, AllocKind thingKind)
|
||||
JSRuntime *rt = zone->runtimeFromAnyThread();
|
||||
if (!maybeLock.locked())
|
||||
maybeLock.lock(rt);
|
||||
Chunk *chunk = rt->gc.pickChunk(zone);
|
||||
Chunk *chunk = rt->gc.pickChunk(zone, maybeStartBackgroundAllocation);
|
||||
if (!chunk)
|
||||
return nullptr;
|
||||
|
||||
@ -1620,7 +1647,8 @@ ArenaLists::allocateFromArenaInline(Zone *zone, AllocKind thingKind)
|
||||
void *
|
||||
ArenaLists::allocateFromArena(JS::Zone *zone, AllocKind thingKind)
|
||||
{
|
||||
return allocateFromArenaInline(zone, thingKind);
|
||||
AutoMaybeStartBackgroundAllocation maybeStartBackgroundAllocation;
|
||||
return allocateFromArenaInline(zone, thingKind, maybeStartBackgroundAllocation);
|
||||
}
|
||||
|
||||
void
|
||||
@ -1695,7 +1723,7 @@ ArenaLists::queueForBackgroundSweep(FreeOp *fop, AllocKind thingKind)
|
||||
JS_ASSERT(IsBackgroundFinalized(thingKind));
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
JS_ASSERT(!fop->runtime()->gc.helperThread.sweeping());
|
||||
JS_ASSERT(!fop->runtime()->gc.isBackgroundSweeping());
|
||||
#endif
|
||||
|
||||
ArenaList *al = &arenaLists[thingKind];
|
||||
@ -1867,6 +1895,8 @@ ArenaLists::refillFreeList(ThreadSafeContext *cx, AllocKind thingKind)
|
||||
return thing;
|
||||
}
|
||||
|
||||
AutoMaybeStartBackgroundAllocation maybeStartBackgroundAllocation;
|
||||
|
||||
if (cx->isJSContext()) {
|
||||
/*
|
||||
* allocateFromArena may fail while the background finalization still
|
||||
@ -1877,13 +1907,14 @@ ArenaLists::refillFreeList(ThreadSafeContext *cx, AllocKind thingKind)
|
||||
* this race we always try to allocate twice.
|
||||
*/
|
||||
for (bool secondAttempt = false; ; secondAttempt = true) {
|
||||
void *thing = cx->allocator()->arenas.allocateFromArenaInline(zone, thingKind);
|
||||
void *thing = cx->allocator()->arenas.allocateFromArenaInline(zone, thingKind,
|
||||
maybeStartBackgroundAllocation);
|
||||
if (MOZ_LIKELY(!!thing))
|
||||
return thing;
|
||||
if (secondAttempt)
|
||||
break;
|
||||
|
||||
cx->asJSContext()->runtime()->gc.helperThread.waitBackgroundSweepEnd();
|
||||
cx->asJSContext()->runtime()->gc.waitBackgroundSweepEnd();
|
||||
}
|
||||
} else {
|
||||
#ifdef JS_THREADSAFE
|
||||
@ -1902,7 +1933,8 @@ ArenaLists::refillFreeList(ThreadSafeContext *cx, AllocKind thingKind)
|
||||
WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER);
|
||||
}
|
||||
|
||||
void *thing = cx->allocator()->arenas.allocateFromArenaInline(zone, thingKind);
|
||||
void *thing = cx->allocator()->arenas.allocateFromArenaInline(zone, thingKind,
|
||||
maybeStartBackgroundAllocation);
|
||||
if (thing)
|
||||
return thing;
|
||||
#else
|
||||
@ -2102,7 +2134,7 @@ GCRuntime::maybeGC(Zone *zone)
|
||||
if (zone->gcBytes > 1024 * 1024 &&
|
||||
zone->gcBytes >= factor * zone->gcTriggerBytes &&
|
||||
incrementalState == NO_INCREMENTAL &&
|
||||
!helperThread.sweeping())
|
||||
!isBackgroundSweeping())
|
||||
{
|
||||
PrepareZoneForGC(zone);
|
||||
GCSlice(rt, GC_NORMAL, JS::gcreason::MAYBEGC);
|
||||
@ -2284,6 +2316,7 @@ SweepBackgroundThings(JSRuntime* rt, bool onBackgroundThread)
|
||||
static void
|
||||
AssertBackgroundSweepingFinished(JSRuntime *rt)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
JS_ASSERT(!rt->gc.sweepingZones);
|
||||
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
|
||||
for (unsigned i = 0; i < FINALIZE_LIMIT; ++i) {
|
||||
@ -2291,6 +2324,7 @@ AssertBackgroundSweepingFinished(JSRuntime *rt)
|
||||
JS_ASSERT(zone->allocator.arenas.doneBackgroundFinalize(AllocKind(i)));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned
|
||||
@ -2312,7 +2346,7 @@ js::GetCPUCount()
|
||||
#endif /* JS_THREADSAFE */
|
||||
|
||||
bool
|
||||
GCHelperThread::init()
|
||||
GCHelperState::init()
|
||||
{
|
||||
if (!rt->useHelperThreads()) {
|
||||
backgroundAllocation = false;
|
||||
@ -2320,174 +2354,172 @@ GCHelperThread::init()
|
||||
}
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
if (!(wakeup = PR_NewCondVar(rt->gc.lock)))
|
||||
return false;
|
||||
if (!(done = PR_NewCondVar(rt->gc.lock)))
|
||||
return false;
|
||||
|
||||
thread = PR_CreateThread(PR_USER_THREAD, threadMain, this, PR_PRIORITY_NORMAL,
|
||||
PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
|
||||
if (!thread)
|
||||
return false;
|
||||
|
||||
backgroundAllocation = (GetCPUCount() >= 2);
|
||||
|
||||
WorkerThreadState().ensureInitialized();
|
||||
#endif /* JS_THREADSAFE */
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
GCHelperThread::finish()
|
||||
GCHelperState::finish()
|
||||
{
|
||||
if (!rt->useHelperThreads() || !rt->gc.lock) {
|
||||
JS_ASSERT(state == IDLE);
|
||||
JS_ASSERT(state_ == IDLE);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
PRThread *join = nullptr;
|
||||
{
|
||||
AutoLockGC lock(rt);
|
||||
if (thread && state != SHUTDOWN) {
|
||||
/*
|
||||
* We cannot be in the ALLOCATING or CANCEL_ALLOCATION states as
|
||||
* the allocations should have been stopped during the last GC.
|
||||
*/
|
||||
JS_ASSERT(state == IDLE || state == SWEEPING);
|
||||
if (state == IDLE)
|
||||
PR_NotifyCondVar(wakeup);
|
||||
state = SHUTDOWN;
|
||||
join = thread;
|
||||
}
|
||||
}
|
||||
if (join) {
|
||||
/* PR_DestroyThread is not necessary. */
|
||||
PR_JoinThread(join);
|
||||
}
|
||||
if (wakeup)
|
||||
PR_DestroyCondVar(wakeup);
|
||||
// Wait for any lingering background sweeping to finish.
|
||||
waitBackgroundSweepEnd();
|
||||
|
||||
if (done)
|
||||
PR_DestroyCondVar(done);
|
||||
#else
|
||||
MOZ_CRASH();
|
||||
#endif /* JS_THREADSAFE */
|
||||
}
|
||||
|
||||
GCHelperState::State
|
||||
GCHelperState::state()
|
||||
{
|
||||
JS_ASSERT(rt->gc.currentThreadOwnsGCLock());
|
||||
return state_;
|
||||
}
|
||||
|
||||
void
|
||||
GCHelperState::setState(State state)
|
||||
{
|
||||
JS_ASSERT(rt->gc.currentThreadOwnsGCLock());
|
||||
state_ = state;
|
||||
}
|
||||
|
||||
void
|
||||
GCHelperState::startBackgroundThread(State newState)
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
#ifdef MOZ_NUWA_PROCESS
|
||||
extern "C" {
|
||||
MFBT_API bool IsNuwaProcess();
|
||||
MFBT_API void NuwaMarkCurrentThread(void (*recreate)(void *), void *arg);
|
||||
}
|
||||
JS_ASSERT(!thread && state() == IDLE && newState != IDLE);
|
||||
setState(newState);
|
||||
|
||||
if (!WorkerThreadState().gcHelperWorklist().append(this))
|
||||
CrashAtUnhandlableOOM("Could not add to pending GC helpers list");
|
||||
WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
|
||||
#else
|
||||
MOZ_CRASH();
|
||||
#endif
|
||||
|
||||
/* static */
|
||||
void
|
||||
GCHelperThread::threadMain(void *arg)
|
||||
{
|
||||
PR_SetCurrentThreadName("JS GC Helper");
|
||||
|
||||
#ifdef MOZ_NUWA_PROCESS
|
||||
if (IsNuwaProcess && IsNuwaProcess()) {
|
||||
JS_ASSERT(NuwaMarkCurrentThread != nullptr);
|
||||
NuwaMarkCurrentThread(nullptr, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
static_cast<GCHelperThread *>(arg)->threadLoop();
|
||||
}
|
||||
|
||||
void
|
||||
GCHelperThread::wait(PRCondVar *which)
|
||||
GCHelperState::waitForBackgroundThread()
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
JS_ASSERT(CurrentThreadCanAccessRuntime(rt));
|
||||
|
||||
rt->gc.lockOwner = nullptr;
|
||||
PR_WaitCondVar(which, PR_INTERVAL_NO_TIMEOUT);
|
||||
PR_WaitCondVar(done, PR_INTERVAL_NO_TIMEOUT);
|
||||
#ifdef DEBUG
|
||||
rt->gc.lockOwner = PR_GetCurrentThread();
|
||||
#endif
|
||||
#else
|
||||
MOZ_CRASH();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
GCHelperThread::threadLoop()
|
||||
GCHelperState::work()
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
AutoLockGC lock(rt);
|
||||
|
||||
TraceLogger *logger = TraceLoggerForCurrentThread();
|
||||
JS_ASSERT(!thread);
|
||||
thread = PR_GetCurrentThread();
|
||||
|
||||
/*
|
||||
* Even on the first iteration the state can be SHUTDOWN or SWEEPING if
|
||||
* the stop request or the GC and the corresponding startBackgroundSweep call
|
||||
* happen before this thread has a chance to run.
|
||||
*/
|
||||
for (;;) {
|
||||
switch (state) {
|
||||
case SHUTDOWN:
|
||||
return;
|
||||
case IDLE:
|
||||
wait(wakeup);
|
||||
break;
|
||||
case SWEEPING: {
|
||||
AutoTraceLog logSweeping(logger, TraceLogger::GCSweeping);
|
||||
doSweep();
|
||||
if (state == SWEEPING)
|
||||
state = IDLE;
|
||||
PR_NotifyAllCondVar(done);
|
||||
break;
|
||||
}
|
||||
case ALLOCATING: {
|
||||
AutoTraceLog logAllocating(logger, TraceLogger::GCAllocation);
|
||||
do {
|
||||
Chunk *chunk;
|
||||
{
|
||||
AutoUnlockGC unlock(rt);
|
||||
chunk = Chunk::allocate(rt);
|
||||
}
|
||||
switch (state()) {
|
||||
|
||||
/* OOM stops the background allocation. */
|
||||
if (!chunk)
|
||||
break;
|
||||
JS_ASSERT(chunk->info.numArenasFreeCommitted == 0);
|
||||
rt->gc.chunkPool.put(chunk);
|
||||
} while (state == ALLOCATING && rt->gc.wantBackgroundAllocation());
|
||||
if (state == ALLOCATING)
|
||||
state = IDLE;
|
||||
break;
|
||||
}
|
||||
case CANCEL_ALLOCATION:
|
||||
state = IDLE;
|
||||
PR_NotifyAllCondVar(done);
|
||||
break;
|
||||
}
|
||||
case IDLE:
|
||||
MOZ_ASSUME_UNREACHABLE("GC helper triggered on idle state");
|
||||
break;
|
||||
|
||||
case SWEEPING: {
|
||||
#if JS_TRACE_LOGGING
|
||||
AutoTraceLog logger(TraceLogging::getLogger(TraceLogging::GC_BACKGROUND),
|
||||
TraceLogging::GC_SWEEPING_START,
|
||||
TraceLogging::GC_SWEEPING_STOP);
|
||||
#endif
|
||||
doSweep();
|
||||
JS_ASSERT(state() == SWEEPING);
|
||||
break;
|
||||
}
|
||||
|
||||
case ALLOCATING: {
|
||||
#if JS_TRACE_LOGGING
|
||||
AutoTraceLog logger(TraceLogging::getLogger(TraceLogging::GC_BACKGROUND),
|
||||
TraceLogging::GC_ALLOCATING_START,
|
||||
TraceLogging::GC_ALLOCATING_STOP);
|
||||
#endif
|
||||
do {
|
||||
Chunk *chunk;
|
||||
{
|
||||
AutoUnlockGC unlock(rt);
|
||||
chunk = Chunk::allocate(rt);
|
||||
}
|
||||
|
||||
/* OOM stops the background allocation. */
|
||||
if (!chunk)
|
||||
break;
|
||||
JS_ASSERT(chunk->info.numArenasFreeCommitted == 0);
|
||||
rt->gc.chunkPool.put(chunk);
|
||||
} while (state() == ALLOCATING && rt->gc.wantBackgroundAllocation());
|
||||
|
||||
JS_ASSERT(state() == ALLOCATING || state() == CANCEL_ALLOCATION);
|
||||
break;
|
||||
}
|
||||
|
||||
case CANCEL_ALLOCATION:
|
||||
break;
|
||||
}
|
||||
|
||||
setState(IDLE);
|
||||
thread = nullptr;
|
||||
|
||||
PR_NotifyAllCondVar(done);
|
||||
#else
|
||||
MOZ_CRASH();
|
||||
#endif
|
||||
}
|
||||
#endif /* JS_THREADSAFE */
|
||||
|
||||
void
|
||||
GCHelperThread::startBackgroundSweep(bool shouldShrink)
|
||||
GCHelperState::startBackgroundSweep(bool shouldShrink)
|
||||
{
|
||||
JS_ASSERT(rt->useHelperThreads());
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
AutoLockWorkerThreadState workerLock;
|
||||
AutoLockGC lock(rt);
|
||||
JS_ASSERT(state == IDLE);
|
||||
JS_ASSERT(state() == IDLE);
|
||||
JS_ASSERT(!sweepFlag);
|
||||
sweepFlag = true;
|
||||
shrinkFlag = shouldShrink;
|
||||
state = SWEEPING;
|
||||
PR_NotifyCondVar(wakeup);
|
||||
startBackgroundThread(SWEEPING);
|
||||
#endif /* JS_THREADSAFE */
|
||||
}
|
||||
|
||||
/* Must be called with the GC lock taken. */
|
||||
void
|
||||
GCHelperThread::startBackgroundShrink()
|
||||
GCHelperState::startBackgroundShrink()
|
||||
{
|
||||
JS_ASSERT(rt->useHelperThreads());
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
switch (state) {
|
||||
switch (state()) {
|
||||
case IDLE:
|
||||
JS_ASSERT(!sweepFlag);
|
||||
shrinkFlag = true;
|
||||
state = SWEEPING;
|
||||
PR_NotifyCondVar(wakeup);
|
||||
startBackgroundThread(SWEEPING);
|
||||
break;
|
||||
case SWEEPING:
|
||||
shrinkFlag = true;
|
||||
@ -2499,43 +2531,41 @@ GCHelperThread::startBackgroundShrink()
|
||||
* shrink.
|
||||
*/
|
||||
break;
|
||||
case SHUTDOWN:
|
||||
MOZ_ASSUME_UNREACHABLE("No shrink on shutdown");
|
||||
}
|
||||
#endif /* JS_THREADSAFE */
|
||||
}
|
||||
|
||||
void
|
||||
GCHelperThread::waitBackgroundSweepEnd()
|
||||
GCHelperState::waitBackgroundSweepEnd()
|
||||
{
|
||||
if (!rt->useHelperThreads()) {
|
||||
JS_ASSERT(state == IDLE);
|
||||
JS_ASSERT(state_ == IDLE);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
AutoLockGC lock(rt);
|
||||
while (state == SWEEPING)
|
||||
wait(done);
|
||||
while (state() == SWEEPING)
|
||||
waitForBackgroundThread();
|
||||
if (rt->gc.incrementalState == NO_INCREMENTAL)
|
||||
AssertBackgroundSweepingFinished(rt);
|
||||
#endif /* JS_THREADSAFE */
|
||||
}
|
||||
|
||||
void
|
||||
GCHelperThread::waitBackgroundSweepOrAllocEnd()
|
||||
GCHelperState::waitBackgroundSweepOrAllocEnd()
|
||||
{
|
||||
if (!rt->useHelperThreads()) {
|
||||
JS_ASSERT(state == IDLE);
|
||||
JS_ASSERT(state_ == IDLE);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
AutoLockGC lock(rt);
|
||||
if (state == ALLOCATING)
|
||||
state = CANCEL_ALLOCATION;
|
||||
while (state == SWEEPING || state == CANCEL_ALLOCATION)
|
||||
wait(done);
|
||||
if (state() == ALLOCATING)
|
||||
setState(CANCEL_ALLOCATION);
|
||||
while (state() == SWEEPING || state() == CANCEL_ALLOCATION)
|
||||
waitForBackgroundThread();
|
||||
if (rt->gc.incrementalState == NO_INCREMENTAL)
|
||||
AssertBackgroundSweepingFinished(rt);
|
||||
#endif /* JS_THREADSAFE */
|
||||
@ -2543,20 +2573,18 @@ GCHelperThread::waitBackgroundSweepOrAllocEnd()
|
||||
|
||||
/* Must be called with the GC lock taken. */
|
||||
inline void
|
||||
GCHelperThread::startBackgroundAllocationIfIdle()
|
||||
GCHelperState::startBackgroundAllocationIfIdle()
|
||||
{
|
||||
JS_ASSERT(rt->useHelperThreads());
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
if (state == IDLE) {
|
||||
state = ALLOCATING;
|
||||
PR_NotifyCondVar(wakeup);
|
||||
}
|
||||
if (state_ == IDLE)
|
||||
startBackgroundThread(ALLOCATING);
|
||||
#endif /* JS_THREADSAFE */
|
||||
}
|
||||
|
||||
void
|
||||
GCHelperThread::replenishAndFreeLater(void *ptr)
|
||||
GCHelperState::replenishAndFreeLater(void *ptr)
|
||||
{
|
||||
JS_ASSERT(freeCursor == freeCursorEnd);
|
||||
do {
|
||||
@ -2577,7 +2605,7 @@ GCHelperThread::replenishAndFreeLater(void *ptr)
|
||||
#ifdef JS_THREADSAFE
|
||||
/* Must be called with the GC lock taken. */
|
||||
void
|
||||
GCHelperThread::doSweep()
|
||||
GCHelperState::doSweep()
|
||||
{
|
||||
if (sweepFlag) {
|
||||
sweepFlag = false;
|
||||
@ -2617,10 +2645,10 @@ GCHelperThread::doSweep()
|
||||
#endif /* JS_THREADSAFE */
|
||||
|
||||
bool
|
||||
GCHelperThread::onBackgroundThread()
|
||||
GCHelperState::onBackgroundThread()
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
return PR_GetCurrentThread() == getThread();
|
||||
return PR_GetCurrentThread() == thread;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
@ -4143,7 +4171,7 @@ GCRuntime::endSweepPhase(JSGCInvocationKind gckind, bool lastGC)
|
||||
/*
|
||||
* Destroy arenas after we finished the sweeping so finalizers can
|
||||
* safely use IsAboutToBeFinalized(). This is done on the
|
||||
* GCHelperThread if possible. We acquire the lock only because
|
||||
* GCHelperState if possible. We acquire the lock only because
|
||||
* Expire needs to unlock it for other callers.
|
||||
*/
|
||||
AutoLockGC lock(rt);
|
||||
@ -4407,7 +4435,7 @@ GCRuntime::resetIncrementalGC(const char *reason)
|
||||
|
||||
{
|
||||
gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
|
||||
helperThread.waitBackgroundSweepOrAllocEnd();
|
||||
rt->gc.waitBackgroundSweepOrAllocEnd();
|
||||
}
|
||||
break;
|
||||
|
||||
@ -4618,7 +4646,7 @@ GCRuntime::incrementalCollectSlice(int64_t budget,
|
||||
endSweepPhase(gckind, lastGC);
|
||||
|
||||
if (sweepOnBackgroundThread)
|
||||
helperThread.startBackgroundSweep(gckind == GC_SHRINK);
|
||||
helperState.startBackgroundSweep(gckind == GC_SHRINK);
|
||||
|
||||
incrementalState = NO_INCREMENTAL;
|
||||
break;
|
||||
@ -4712,7 +4740,7 @@ GCRuntime::gcCycle(bool incremental, int64_t budget, JSGCInvocationKind gckind,
|
||||
*/
|
||||
{
|
||||
gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
|
||||
helperThread.waitBackgroundSweepOrAllocEnd();
|
||||
waitBackgroundSweepOrAllocEnd();
|
||||
}
|
||||
|
||||
State prevState = incrementalState;
|
||||
@ -4959,6 +4987,7 @@ js::PrepareForDebugGC(JSRuntime *rt)
|
||||
JS_FRIEND_API(void)
|
||||
JS::ShrinkGCBuffers(JSRuntime *rt)
|
||||
{
|
||||
AutoLockWorkerThreadState workerLock;
|
||||
AutoLockGC lock(rt);
|
||||
JS_ASSERT(!rt->isHeapBusy());
|
||||
|
||||
|
@ -33,7 +33,6 @@ class ArrayBufferViewObject;
|
||||
class SharedArrayBufferObject;
|
||||
class BaseShape;
|
||||
class DebugScopeObject;
|
||||
class GCHelperThread;
|
||||
class GlobalObject;
|
||||
class LazyScript;
|
||||
class Nursery;
|
||||
@ -355,6 +354,12 @@ GetGCKindSlots(AllocKind thingKind, const Class *clasp)
|
||||
return nslots;
|
||||
}
|
||||
|
||||
// Class to assist in triggering background chunk allocation. This cannot be done
|
||||
// while holding the GC or worker thread state lock due to lock ordering issues.
|
||||
// As a result, the triggering is delayed using this class until neither of the
|
||||
// above locks is held.
|
||||
class AutoMaybeStartBackgroundAllocation;
|
||||
|
||||
/*
|
||||
* Arena lists have a head and a cursor. The cursor conceptually lies on arena
|
||||
* boundaries, i.e. before the first arena, between two arenas, or after the
|
||||
@ -771,7 +776,8 @@ class ArenaLists
|
||||
inline void queueForBackgroundSweep(FreeOp *fop, AllocKind thingKind);
|
||||
|
||||
void *allocateFromArena(JS::Zone *zone, AllocKind thingKind);
|
||||
inline void *allocateFromArenaInline(JS::Zone *zone, AllocKind thingKind);
|
||||
inline void *allocateFromArenaInline(JS::Zone *zone, AllocKind thingKind,
|
||||
AutoMaybeStartBackgroundAllocation &maybeStartBackgroundAllocation);
|
||||
|
||||
inline void normalizeBackgroundFinalizeState(AllocKind thingKind);
|
||||
|
||||
@ -914,20 +920,20 @@ extern void
|
||||
NotifyGCPostSwap(JSObject *a, JSObject *b, unsigned preResult);
|
||||
|
||||
/*
|
||||
* Helper that implements sweeping and allocation for kinds that can be swept
|
||||
* and allocated off the main thread.
|
||||
* Helper state for use when JS helper threads sweep and allocate GC thing kinds
|
||||
* that can be swept and allocated off the main thread.
|
||||
*
|
||||
* In non-threadsafe builds, all actual sweeping and allocation is performed
|
||||
* on the main thread, but GCHelperThread encapsulates this from clients as
|
||||
* on the main thread, but GCHelperState encapsulates this from clients as
|
||||
* much as possible.
|
||||
*/
|
||||
class GCHelperThread {
|
||||
class GCHelperState
|
||||
{
|
||||
enum State {
|
||||
IDLE,
|
||||
SWEEPING,
|
||||
ALLOCATING,
|
||||
CANCEL_ALLOCATION,
|
||||
SHUTDOWN
|
||||
CANCEL_ALLOCATION
|
||||
};
|
||||
|
||||
/*
|
||||
@ -943,13 +949,25 @@ class GCHelperThread {
|
||||
static const size_t FREE_ARRAY_SIZE = size_t(1) << 16;
|
||||
static const size_t FREE_ARRAY_LENGTH = FREE_ARRAY_SIZE / sizeof(void *);
|
||||
|
||||
JSRuntime *const rt;
|
||||
PRThread *thread;
|
||||
PRCondVar *wakeup;
|
||||
PRCondVar *done;
|
||||
volatile State state;
|
||||
// Associated runtime.
|
||||
JSRuntime *const rt;
|
||||
|
||||
void wait(PRCondVar *which);
|
||||
// Condvar for notifying the main thread when work has finished. This is
|
||||
// associated with the runtime's GC lock --- the worker thread state
|
||||
// condvars can't be used here due to lock ordering issues.
|
||||
PRCondVar *done;
|
||||
|
||||
// Activity for the helper to do, protected by the GC lock.
|
||||
State state_;
|
||||
|
||||
// Thread which work is being performed on, or null.
|
||||
PRThread *thread;
|
||||
|
||||
void startBackgroundThread(State newState);
|
||||
void waitForBackgroundThread();
|
||||
|
||||
State state();
|
||||
void setState(State state);
|
||||
|
||||
bool sweepFlag;
|
||||
bool shrinkFlag;
|
||||
@ -972,19 +990,15 @@ class GCHelperThread {
|
||||
js_free(array);
|
||||
}
|
||||
|
||||
static void threadMain(void* arg);
|
||||
void threadLoop();
|
||||
|
||||
/* Must be called with the GC lock taken. */
|
||||
void doSweep();
|
||||
|
||||
public:
|
||||
GCHelperThread(JSRuntime *rt)
|
||||
GCHelperState(JSRuntime *rt)
|
||||
: rt(rt),
|
||||
thread(nullptr),
|
||||
wakeup(nullptr),
|
||||
done(nullptr),
|
||||
state(IDLE),
|
||||
state_(IDLE),
|
||||
thread(nullptr),
|
||||
sweepFlag(false),
|
||||
shrinkFlag(false),
|
||||
freeCursor(nullptr),
|
||||
@ -995,6 +1009,8 @@ class GCHelperThread {
|
||||
bool init();
|
||||
void finish();
|
||||
|
||||
void work();
|
||||
|
||||
/* Must be called with the GC lock taken. */
|
||||
void startBackgroundSweep(bool shouldShrink);
|
||||
|
||||
@ -1008,7 +1024,7 @@ class GCHelperThread {
|
||||
void waitBackgroundSweepOrAllocEnd();
|
||||
|
||||
/* Must be called with the GC lock taken. */
|
||||
inline void startBackgroundAllocationIfIdle();
|
||||
void startBackgroundAllocationIfIdle();
|
||||
|
||||
bool canBackgroundAllocate() const {
|
||||
return backgroundAllocation;
|
||||
@ -1018,27 +1034,23 @@ class GCHelperThread {
|
||||
backgroundAllocation = false;
|
||||
}
|
||||
|
||||
PRThread *getThread() const {
|
||||
return thread;
|
||||
}
|
||||
|
||||
bool onBackgroundThread();
|
||||
|
||||
/*
|
||||
* Outside the GC lock may give true answer when in fact the sweeping has
|
||||
* been done.
|
||||
*/
|
||||
bool sweeping() const {
|
||||
return state == SWEEPING;
|
||||
bool isBackgroundSweeping() const {
|
||||
return state_ == SWEEPING;
|
||||
}
|
||||
|
||||
bool shouldShrink() const {
|
||||
JS_ASSERT(sweeping());
|
||||
JS_ASSERT(isBackgroundSweeping());
|
||||
return shrinkFlag;
|
||||
}
|
||||
|
||||
void freeLater(void *ptr) {
|
||||
JS_ASSERT(!sweeping());
|
||||
JS_ASSERT(!isBackgroundSweeping());
|
||||
if (freeCursor != freeCursorEnd)
|
||||
*freeCursor++ = ptr;
|
||||
else
|
||||
|
@ -578,6 +578,12 @@ GlobalWorkerThreadState::canStartCompressionTask()
|
||||
return !compressionWorklist().empty();
|
||||
}
|
||||
|
||||
bool
|
||||
GlobalWorkerThreadState::canStartGCHelperTask()
|
||||
{
|
||||
return !gcHelperWorklist().empty();
|
||||
}
|
||||
|
||||
static void
|
||||
CallNewScriptHookForAllScripts(JSContext *cx, HandleScript script)
|
||||
{
|
||||
@ -1020,6 +1026,24 @@ GlobalWorkerThreadState::compressionTaskForSource(ScriptSource *ss)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
WorkerThread::handleGCHelperWorkload()
|
||||
{
|
||||
JS_ASSERT(WorkerThreadState().isLocked());
|
||||
JS_ASSERT(WorkerThreadState().canStartGCHelperTask());
|
||||
JS_ASSERT(idle());
|
||||
|
||||
JS_ASSERT(!gcHelperState);
|
||||
gcHelperState = WorkerThreadState().gcHelperWorklist().popCopy();
|
||||
|
||||
{
|
||||
AutoUnlockWorkerThreadState unlock;
|
||||
gcHelperState->work();
|
||||
}
|
||||
|
||||
gcHelperState = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
WorkerThread::threadLoop()
|
||||
{
|
||||
@ -1048,7 +1072,8 @@ WorkerThread::threadLoop()
|
||||
if (WorkerThreadState().canStartIonCompile() ||
|
||||
WorkerThreadState().canStartAsmJSCompile() ||
|
||||
WorkerThreadState().canStartParseTask() ||
|
||||
WorkerThreadState().canStartCompressionTask())
|
||||
WorkerThreadState().canStartCompressionTask() ||
|
||||
WorkerThreadState().canStartGCHelperTask())
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -1064,6 +1089,8 @@ WorkerThread::threadLoop()
|
||||
handleParseWorkload();
|
||||
else if (WorkerThreadState().canStartCompressionTask())
|
||||
handleCompressionWorkload();
|
||||
else if (WorkerThreadState().canStartGCHelperTask())
|
||||
handleGCHelperWorkload();
|
||||
else
|
||||
MOZ_ASSUME_UNREACHABLE("No task to perform");
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ class GlobalWorkerThreadState
|
||||
typedef Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> AsmJSParallelTaskVector;
|
||||
typedef Vector<ParseTask*, 0, SystemAllocPolicy> ParseTaskVector;
|
||||
typedef Vector<SourceCompressionTask*, 0, SystemAllocPolicy> SourceCompressionTaskVector;
|
||||
typedef Vector<GCHelperState *, 0, SystemAllocPolicy> GCHelperStateVector;
|
||||
|
||||
// List of available threads, or null if the thread state has not been initialized.
|
||||
WorkerThread *threads;
|
||||
@ -81,6 +82,9 @@ class GlobalWorkerThreadState
|
||||
// Source compression worklist.
|
||||
SourceCompressionTaskVector compressionWorklist_;
|
||||
|
||||
// Runtimes which have sweeping / allocating work to do.
|
||||
GCHelperStateVector gcHelperWorklist_;
|
||||
|
||||
public:
|
||||
GlobalWorkerThreadState();
|
||||
|
||||
@ -150,10 +154,16 @@ class GlobalWorkerThreadState
|
||||
return compressionWorklist_;
|
||||
}
|
||||
|
||||
GCHelperStateVector &gcHelperWorklist() {
|
||||
JS_ASSERT(isLocked());
|
||||
return gcHelperWorklist_;
|
||||
}
|
||||
|
||||
bool canStartAsmJSCompile();
|
||||
bool canStartIonCompile();
|
||||
bool canStartParseTask();
|
||||
bool canStartCompressionTask();
|
||||
bool canStartGCHelperTask();
|
||||
|
||||
uint32_t harvestFailedAsmJSJobs() {
|
||||
JS_ASSERT(isLocked());
|
||||
@ -240,8 +250,11 @@ struct WorkerThread
|
||||
/* Any source being compressed on this thread. */
|
||||
SourceCompressionTask *compressionTask;
|
||||
|
||||
/* Any GC state for background sweeping or allocating being performed. */
|
||||
GCHelperState *gcHelperState;
|
||||
|
||||
bool idle() const {
|
||||
return !ionBuilder && !asmData && !parseTask && !compressionTask;
|
||||
return !ionBuilder && !asmData && !parseTask && !compressionTask && !gcHelperState;
|
||||
}
|
||||
|
||||
void destroy();
|
||||
@ -250,6 +263,7 @@ struct WorkerThread
|
||||
void handleIonWorkload();
|
||||
void handleParseWorkload();
|
||||
void handleCompressionWorkload();
|
||||
void handleGCHelperWorkload();
|
||||
|
||||
static void ThreadMain(void *arg);
|
||||
void threadLoop();
|
||||
|
Loading…
Reference in New Issue
Block a user