From 5e0f361bec9962586b652c28445b4f52208c46f9 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Fri, 31 Jan 2014 18:58:16 -0700 Subject: [PATCH] Bug 941805 - Make the pool of JS workers be per process rather than per runtime, r=billm. --- js/src/builtin/TestingFunctions.cpp | 2 +- js/src/jit/AsmJS.cpp | 92 +++--- js/src/jit/Ion.cpp | 57 ++-- js/src/jit/JitCompartment.h | 12 - js/src/jsapi.cpp | 2 +- js/src/jscntxt.cpp | 3 - js/src/jscntxt.h | 15 +- js/src/jsgc.cpp | 10 +- js/src/jsscript.cpp | 4 +- js/src/jsworkers.cpp | 495 ++++++++++++++-------------- js/src/jsworkers.h | 200 +++++++---- js/src/shell/js.cpp | 2 +- js/src/vm/Runtime.cpp | 58 +--- js/src/vm/Runtime.h | 105 +++--- js/src/vm/ThreadPool.cpp | 2 +- 15 files changed, 536 insertions(+), 523 deletions(-) diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index f42e58baf02..3e98d26b8de 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1405,7 +1405,7 @@ static bool WorkerThreadCount(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); - args.rval().setNumber(static_cast(cx->runtime()->workerThreadCount())); + args.rval().setInt32(cx->runtime()->useHelperThreads() ? WorkerThreadState().threadCount : 0); return true; } diff --git a/js/src/jit/AsmJS.cpp b/js/src/jit/AsmJS.cpp index d3644cfb2a4..30a8a300cff 100644 --- a/js/src/jit/AsmJS.cpp +++ b/js/src/jit/AsmJS.cpp @@ -5364,20 +5364,20 @@ CheckFunctionsSequential(ModuleCompiler &m) // on rt->workerThreadState->asmJSCompilationInProgress. class ParallelCompilationGuard { - WorkerThreadState *parallelState_; + bool parallelState_; public: - ParallelCompilationGuard() : parallelState_(nullptr) {} + ParallelCompilationGuard() : parallelState_(false) {} ~ParallelCompilationGuard() { if (parallelState_) { - JS_ASSERT(parallelState_->asmJSCompilationInProgress == true); - parallelState_->asmJSCompilationInProgress = false; + JS_ASSERT(WorkerThreadState().asmJSCompilationInProgress == true); + WorkerThreadState().asmJSCompilationInProgress = false; } } - bool claim(WorkerThreadState *state) { + bool claim() { JS_ASSERT(!parallelState_); - if (!state->asmJSCompilationInProgress.compareExchange(false, true)) + if (!WorkerThreadState().asmJSCompilationInProgress.compareExchange(false, true)) return false; - parallelState_ = state; + parallelState_ = true; return true; } }; @@ -5391,22 +5391,23 @@ ParallelCompilationEnabled(ExclusiveContext *cx) // parsing task, ensure that there another free thread to avoid deadlock. // (Note: there is at most one thread used for parsing so we don't have to // worry about general dining philosophers.) - if (!cx->isJSContext()) - return cx->workerThreadState()->numThreads > 1; + if (WorkerThreadState().threadCount <= 1) + return false; + if (!cx->isJSContext()) + return true; return cx->asJSContext()->runtime()->canUseParallelIonCompilation(); } // State of compilation as tracked and updated by the main thread. struct ParallelGroupState { - WorkerThreadState &state; js::Vector &tasks; int32_t outstandingJobs; // Good work, jobs! uint32_t compiledJobs; - ParallelGroupState(WorkerThreadState &state, js::Vector &tasks) - : state(state), tasks(tasks), outstandingJobs(0), compiledJobs(0) + ParallelGroupState(js::Vector &tasks) + : tasks(tasks), outstandingJobs(0), compiledJobs(0) { } }; @@ -5414,14 +5415,14 @@ struct ParallelGroupState static AsmJSParallelTask * GetFinishedCompilation(ModuleCompiler &m, ParallelGroupState &group) { - AutoLockWorkerThreadState lock(*m.cx()->workerThreadState()); + AutoLockWorkerThreadState lock; - while (!group.state.asmJSWorkerFailed()) { - if (!group.state.asmJSFinishedList.empty()) { + while (!WorkerThreadState().asmJSWorkerFailed()) { + if (!WorkerThreadState().asmJSFinishedList().empty()) { group.outstandingJobs--; - return group.state.asmJSFinishedList.popCopy(); + return WorkerThreadState().asmJSFinishedList().popCopy(); } - group.state.wait(WorkerThreadState::CONSUMER); + WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER); } return nullptr; @@ -5471,9 +5472,14 @@ GetUnusedTask(ParallelGroupState &group, uint32_t i, AsmJSParallelTask **outTask static bool CheckFunctionsParallelImpl(ModuleCompiler &m, ParallelGroupState &group) { - JS_ASSERT(group.state.asmJSWorklist.empty()); - JS_ASSERT(group.state.asmJSFinishedList.empty()); - group.state.resetAsmJSFailureState(); +#ifdef DEBUG + { + AutoLockWorkerThreadState lock; + JS_ASSERT(WorkerThreadState().asmJSWorklist().empty()); + JS_ASSERT(WorkerThreadState().asmJSFinishedList().empty()); + } +#endif + WorkerThreadState().resetAsmJSFailureState(); for (unsigned i = 0; PeekToken(m.parser()) == TOK_FUNCTION; i++) { // Get exclusive access to an empty LifoAlloc from the thread group's pool. @@ -5488,7 +5494,7 @@ CheckFunctionsParallelImpl(ModuleCompiler &m, ParallelGroupState &group) return false; // Perform optimizations and LIR generation on a worker thread. - task->init(func, mir); + task->init(m.cx()->compartment()->runtimeFromAnyThread(), func, mir); if (!StartOffThreadAsmJSCompile(m.cx(), task)) return false; @@ -5507,9 +5513,14 @@ CheckFunctionsParallelImpl(ModuleCompiler &m, ParallelGroupState &group) JS_ASSERT(group.outstandingJobs == 0); JS_ASSERT(group.compiledJobs == m.numFunctions()); - JS_ASSERT(group.state.asmJSWorklist.empty()); - JS_ASSERT(group.state.asmJSFinishedList.empty()); - JS_ASSERT(!group.state.asmJSWorkerFailed()); +#ifdef DEBUG + { + AutoLockWorkerThreadState lock; + JS_ASSERT(WorkerThreadState().asmJSWorklist().empty()); + JS_ASSERT(WorkerThreadState().asmJSFinishedList().empty()); + } +#endif + JS_ASSERT(!WorkerThreadState().asmJSWorkerFailed()); return true; } @@ -5526,32 +5537,32 @@ CancelOutstandingJobs(ModuleCompiler &m, ParallelGroupState &group) if (!group.outstandingJobs) return; - AutoLockWorkerThreadState lock(*m.cx()->workerThreadState()); + AutoLockWorkerThreadState lock; // From the compiling tasks, eliminate those waiting for worker assignation. - group.outstandingJobs -= group.state.asmJSWorklist.length(); - group.state.asmJSWorklist.clear(); + group.outstandingJobs -= WorkerThreadState().asmJSWorklist().length(); + WorkerThreadState().asmJSWorklist().clear(); // From the compiling tasks, eliminate those waiting for codegen. - group.outstandingJobs -= group.state.asmJSFinishedList.length(); - group.state.asmJSFinishedList.clear(); + group.outstandingJobs -= WorkerThreadState().asmJSFinishedList().length(); + WorkerThreadState().asmJSFinishedList().clear(); // Eliminate tasks that failed without adding to the finished list. - group.outstandingJobs -= group.state.harvestFailedAsmJSJobs(); + group.outstandingJobs -= WorkerThreadState().harvestFailedAsmJSJobs(); // Any remaining tasks are therefore undergoing active compilation. JS_ASSERT(group.outstandingJobs >= 0); while (group.outstandingJobs > 0) { - group.state.wait(WorkerThreadState::CONSUMER); + WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER); - group.outstandingJobs -= group.state.harvestFailedAsmJSJobs(); - group.outstandingJobs -= group.state.asmJSFinishedList.length(); - group.state.asmJSFinishedList.clear(); + group.outstandingJobs -= WorkerThreadState().harvestFailedAsmJSJobs(); + group.outstandingJobs -= WorkerThreadState().asmJSFinishedList().length(); + WorkerThreadState().asmJSFinishedList().clear(); } JS_ASSERT(group.outstandingJobs == 0); - JS_ASSERT(group.state.asmJSWorklist.empty()); - JS_ASSERT(group.state.asmJSFinishedList.empty()); + JS_ASSERT(WorkerThreadState().asmJSWorklist().empty()); + JS_ASSERT(WorkerThreadState().asmJSFinishedList().empty()); } static const size_t LIFO_ALLOC_PARALLEL_CHUNK_SIZE = 1 << 12; @@ -5565,12 +5576,11 @@ CheckFunctionsParallel(ModuleCompiler &m) // constraint by hoisting asmJS* state out of WorkerThreadState so multiple // concurrent asm.js parallel compilations don't race.) ParallelCompilationGuard g; - if (!ParallelCompilationEnabled(m.cx()) || !g.claim(m.cx()->workerThreadState())) + if (!ParallelCompilationEnabled(m.cx()) || !g.claim()) return CheckFunctionsSequential(m); // Saturate all worker threads plus the main thread. - WorkerThreadState &state = *m.cx()->workerThreadState(); - size_t numParallelJobs = state.numThreads + 1; + size_t numParallelJobs = WorkerThreadState().threadCount + 1; // Allocate scoped AsmJSParallelTask objects. Each contains a unique // LifoAlloc that provides all necessary memory for compilation. @@ -5582,12 +5592,12 @@ CheckFunctionsParallel(ModuleCompiler &m) tasks.infallibleAppend(LIFO_ALLOC_PARALLEL_CHUNK_SIZE); // With compilation memory in-scope, dispatch worker threads. - ParallelGroupState group(state, tasks); + ParallelGroupState group(tasks); if (!CheckFunctionsParallelImpl(m, group)) { CancelOutstandingJobs(m, group); // If failure was triggered by a worker thread, report error. - if (void *maybeFunc = state.maybeAsmJSFailedFunction()) { + if (void *maybeFunc = WorkerThreadState().maybeAsmJSFailedFunction()) { ModuleCompiler::Func *func = reinterpret_cast(maybeFunc); return m.failOffset(func->srcOffset(), "allocation failure during compilation"); } diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index ce9d78b3aa4..c33cfbe0d65 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -513,15 +513,18 @@ jit::FinishOffThreadBuilder(IonBuilder *builder) } static inline void -FinishAllOffThreadCompilations(JitCompartment *ion) +FinishAllOffThreadCompilations(JSCompartment *comp) { - OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations(); + AutoLockWorkerThreadState lock; + GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList(); - for (size_t i = 0; i < compilations.length(); i++) { - IonBuilder *builder = compilations[i]; - FinishOffThreadBuilder(builder); + for (size_t i = 0; i < finished.length(); i++) { + IonBuilder *builder = finished[i]; + if (builder->compartment == CompileCompartment::get(comp)) { + FinishOffThreadBuilder(builder); + WorkerThreadState().remove(finished, &i); + } } - compilations.clear(); } /* static */ void @@ -543,7 +546,7 @@ JitCompartment::mark(JSTracer *trc, JSCompartment *compartment) // do this for minor GCs. JS_ASSERT(!trc->runtime->isHeapMinorCollecting()); CancelOffThreadIonCompile(compartment, nullptr); - FinishAllOffThreadCompilations(this); + FinishAllOffThreadCompilations(compartment); // Free temporary OSR buffer. rt->freeOsrTempData(); @@ -1512,18 +1515,30 @@ AttachFinishedCompilations(JSContext *cx) { #ifdef JS_THREADSAFE JitCompartment *ion = cx->compartment()->jitCompartment(); - if (!ion || !cx->runtime()->workerThreadState) + if (!ion) return; types::AutoEnterAnalysis enterTypes(cx); - AutoLockWorkerThreadState lock(*cx->runtime()->workerThreadState); + AutoLockWorkerThreadState lock; - OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations(); + GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList(); - // Incorporate any off thread compilations which have finished, failed or - // have been cancelled. - while (!compilations.empty()) { - IonBuilder *builder = compilations.popCopy(); + // Incorporate any off thread compilations for the compartment which have + // finished, failed or have been cancelled. + while (true) { + IonBuilder *builder = nullptr; + + // Find a finished builder for the compartment. + for (size_t i = 0; i < finished.length(); i++) { + IonBuilder *testBuilder = finished[i]; + if (testBuilder->compartment == CompileCompartment::get(cx->compartment())) { + builder = testBuilder; + WorkerThreadState().remove(finished, &i); + break; + } + } + if (!builder) + break; if (CodeGenerator *codegen = builder->backgroundCodegen()) { RootedScript script(cx, builder->script()); @@ -1538,7 +1553,7 @@ AttachFinishedCompilations(JSContext *cx) { // Release the worker thread lock and root the compiler for GC. AutoTempAllocatorRooter root(cx, &builder->alloc()); - AutoUnlockWorkerThreadState unlock(cx->runtime()); + AutoUnlockWorkerThreadState unlock; AutoFlushCache afc("AttachFinishedCompilations", cx->runtime()->jitRuntime()); success = codegen->link(cx, builder->constraints()); } @@ -1555,8 +1570,6 @@ AttachFinishedCompilations(JSContext *cx) FinishOffThreadBuilder(builder); } - - compilations.clear(); #endif } @@ -1569,10 +1582,14 @@ OffThreadCompilationAvailable(JSContext *cx) // on the main thread in some cases. Do not compile off thread during an // incremental GC, as this may trip incremental read barriers. // + // Require cpuCount > 1 so that Ion compilation jobs and main-thread + // execution are not competing for the same resources. + // // Skip off thread compilation if PC count profiling is enabled, as // CodeGenerator::maybeCreateScriptCounts will not attach script profiles // when running off thread. return cx->runtime()->canUseParallelIonCompilation() + && WorkerThreadState().cpuCount > 1 && cx->runtime()->gcIncrementalState == gc::NO_INCREMENTAL && !cx->runtime()->profilingScripts; } @@ -1832,7 +1849,9 @@ CheckScriptSize(JSContext *cx, JSScript* script) if (script->length() > MAX_MAIN_THREAD_SCRIPT_SIZE || numLocalsAndArgs > MAX_MAIN_THREAD_LOCALS_AND_ARGS) { - if (cx->runtime()->canUseParallelIonCompilation()) { + if (cx->runtime()->canUseParallelIonCompilation() && + WorkerThreadState().cpuCount > 1) + { // Even if off thread compilation is enabled, there are cases where // compilation must still occur on the main thread. Don't compile // in these cases (except when profiling scripts, as compilations @@ -2489,7 +2508,7 @@ jit::StopAllOffThreadCompilations(JSCompartment *comp) if (!comp->jitCompartment()) return; CancelOffThreadIonCompile(comp, nullptr); - FinishAllOffThreadCompilations(comp->jitCompartment()); + FinishAllOffThreadCompilations(comp); } void diff --git a/js/src/jit/JitCompartment.h b/js/src/jit/JitCompartment.h index 97cb2eecdf5..aa28b54f42b 100644 --- a/js/src/jit/JitCompartment.h +++ b/js/src/jit/JitCompartment.h @@ -59,8 +59,6 @@ typedef void (*EnterJitCode)(void *code, unsigned argc, Value *argv, StackFrame class IonBuilder; -typedef Vector OffThreadCompilationVector; - // ICStubSpace is an abstraction for allocation policy and storage for stub data. // There are two kinds of stubs: optimized stubs and fallback stubs (the latter // also includes stubs that can make non-tail calls that can GC). @@ -332,12 +330,6 @@ class JitCompartment // Ion state for the compartment's runtime. JitRuntime *rt; - // Any scripts for which off thread compilation has successfully finished, - // failed, or been cancelled. All off thread compilations which are started - // will eventually appear in this list asynchronously. Protected by the - // runtime's analysis lock. - OffThreadCompilationVector finishedOffThreadCompilations_; - // Map ICStub keys to ICStub shared code objects. typedef WeakValueCache > ICStubCodeMap; ICStubCodeMap *stubCodes_; @@ -361,10 +353,6 @@ class JitCompartment JitCode *generateStringConcatStub(JSContext *cx, ExecutionMode mode); public: - OffThreadCompilationVector &finishedOffThreadCompilations() { - return finishedOffThreadCompilations_; - } - JitCode *getStubCode(uint32_t key) { ICStubCodeMap::AddPtr p = stubCodes_->lookupForAdd(key); if (p) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 1430593855a..58501dbd3d2 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4529,7 +4529,7 @@ JS::FinishOffThreadScript(JSContext *maybecx, JSRuntime *rt, void *token) if (maybecx) lfc.construct(maybecx); - return rt->workerThreadState->finishParseTask(maybecx, rt, token); + return WorkerThreadState().finishParseTask(maybecx, rt, token); #else MOZ_ASSUME_UNREACHABLE("Off thread compilation is not available."); #endif diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 33f24aa6836..1d3da1f8216 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -1052,9 +1052,6 @@ js::ThreadSafeContext::ThreadSafeContext(JSRuntime *rt, PerThreadData *pt, Conte perThreadData(pt), allocator_(nullptr) { -#ifdef JS_THREADSAFE - JS_ASSERT_IF(kind == Context_Exclusive, rt->workerThreadState != nullptr); -#endif } bool diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index dfc00f2724b..675f80622db 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -282,8 +282,6 @@ struct ThreadSafeContext : ContextFriendFields, PropertyName *emptyString() { return runtime_->emptyString; } FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); } bool useHelperThreads() { return runtime_->useHelperThreads(); } - unsigned cpuCount() { return runtime_->cpuCount(); } - size_t workerThreadCount() { return runtime_->workerThreadCount(); } void *runtimeAddressForJit() { return runtime_; } void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; } void *stackLimitAddressForJitCode(StackKind kind); @@ -390,15 +388,6 @@ class ExclusiveContext : public ThreadSafeContext return runtime_->scriptDataTable(); } -#ifdef JS_THREADSAFE - // Since JSRuntime::workerThreadState is necessarily initialized from the - // main thread before the first worker thread can access it, there is no - // possibility for a race read/writing it. - WorkerThreadState *workerThreadState() { - return runtime_->workerThreadState; - } -#endif - // Methods specific to any WorkerThread for the context. frontend::CompileError &addPendingCompileError(); void addPendingOverRecursed(); @@ -1040,7 +1029,7 @@ class AutoLockForExclusiveAccess void init(JSRuntime *rt) { runtime = rt; if (runtime->numExclusiveThreads) { - runtime->assertCanLock(JSRuntime::ExclusiveAccessLock); + runtime->assertCanLock(ExclusiveAccessLock); PR_Lock(runtime->exclusiveAccessLock); #ifdef DEBUG runtime->exclusiveAccessOwner = PR_GetCurrentThread(); @@ -1095,7 +1084,7 @@ class AutoLockForCompilation void init(JSRuntime *rt) { runtime = rt; if (runtime->numCompilationThreads) { - runtime->assertCanLock(JSRuntime::CompilationLock); + runtime->assertCanLock(CompilationLock); PR_Lock(runtime->compilationLock); #ifdef DEBUG runtime->compilationLockOwner = PR_GetCurrentThread(); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index f54d3e3a24e..7d1614e07b0 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1713,9 +1713,9 @@ ArenaLists::refillFreeList(ThreadSafeContext *cx, AllocKind thingKind) mozilla::Maybe lock; JSRuntime *rt = zone->runtimeFromAnyThread(); if (rt->exclusiveThreadsPresent()) { - lock.construct(*rt->workerThreadState); + lock.construct(); while (rt->isHeapBusy()) - rt->workerThreadState->wait(WorkerThreadState::PRODUCER); + WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER); } void *thing = cx->allocator()->arenas.allocateFromArenaInline(zone, thingKind); @@ -4300,7 +4300,7 @@ AutoTraceSession::AutoTraceSession(JSRuntime *rt, js::HeapState heapState) // Lock the worker thread state when changing the heap state in the // presence of exclusive threads, to avoid racing with refillFreeList. #ifdef JS_THREADSAFE - AutoLockWorkerThreadState lock(*rt->workerThreadState); + AutoLockWorkerThreadState lock; rt->heapState = heapState; #else MOZ_CRASH(); @@ -4316,11 +4316,11 @@ AutoTraceSession::~AutoTraceSession() if (runtime->exclusiveThreadsPresent()) { #ifdef JS_THREADSAFE - AutoLockWorkerThreadState lock(*runtime->workerThreadState); + AutoLockWorkerThreadState lock; runtime->heapState = prevState; // Notify any worker threads waiting for the trace session to end. - runtime->workerThreadState->notifyAll(WorkerThreadState::PRODUCER); + WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER); #else MOZ_CRASH(); #endif diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index b3581ec7706..0e9dda52d19 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1304,8 +1304,8 @@ ScriptSource::setSourceCopy(ExclusiveContext *cx, const jschar *src, uint32_t le // progress on our compression task. const size_t HUGE_SCRIPT = 5 * 1024 * 1024; if (length < HUGE_SCRIPT && - cx->cpuCount() > 1 && - cx->workerThreadCount() >= 2) + WorkerThreadState().cpuCount > 1 && + WorkerThreadState().threadCount >= 2) { task->ss = this; task->chars = src; diff --git a/js/src/jsworkers.cpp b/js/src/jsworkers.cpp index 88699f89bb9..f2c50bfac98 100644 --- a/js/src/jsworkers.cpp +++ b/js/src/jsworkers.cpp @@ -14,7 +14,6 @@ #include "prmjtime.h" #include "frontend/BytecodeCompiler.h" -#include "jit/ExecutionModeInlines.h" #include "jit/IonBuilder.h" #include "vm/Debugger.h" @@ -28,31 +27,37 @@ using namespace js; using mozilla::ArrayLength; using mozilla::DebugOnly; +namespace js { + +GlobalWorkerThreadState gWorkerThreadState; + +} // namespace js + bool js::EnsureWorkerThreadsInitialized(ExclusiveContext *cx) { // If 'cx' is not a JSContext, we are already off the main thread and the // worker threads would have already been initialized. - if (!cx->isJSContext()) { - JS_ASSERT(cx->workerThreadState() != nullptr); - return true; - } - - JSRuntime *rt = cx->asJSContext()->runtime(); - if (rt->workerThreadState) + if (!cx->isJSContext()) return true; - rt->workerThreadState = rt->new_(rt); - if (!rt->workerThreadState) - return false; + return WorkerThreadState().ensureInitialized(); +} - if (!rt->workerThreadState->init()) { - js_delete(rt->workerThreadState); - rt->workerThreadState = nullptr; - return false; - } +static size_t +ThreadCountForCPUCount(size_t cpuCount) +{ + return Max(cpuCount, (size_t)2); +} - return true; +void +js::SetFakeCPUCount(size_t count) +{ + // This must be called before the threads have been initialized. + JS_ASSERT(!WorkerThreadState().threads); + + WorkerThreadState().cpuCount = count; + WorkerThreadState().threadCount = ThreadCountForCPUCount(count); } #ifdef JS_ION @@ -61,23 +66,19 @@ bool js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData) { // Threads already initialized by the AsmJS compiler. - JS_ASSERT(cx->workerThreadState() != nullptr); JS_ASSERT(asmData->mir); JS_ASSERT(asmData->lir == nullptr); - WorkerThreadState &state = *cx->workerThreadState(); - JS_ASSERT(state.numThreads); - - AutoLockWorkerThreadState lock(state); + AutoLockWorkerThreadState lock; // Don't append this task if another failed. - if (state.asmJSWorkerFailed()) + if (WorkerThreadState().asmJSWorkerFailed()) return false; - if (!state.asmJSWorklist.append(asmData)) + if (!WorkerThreadState().asmJSWorklist().append(asmData)) return false; - state.notifyOne(WorkerThreadState::PRODUCER); + WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER); return true; } @@ -87,33 +88,26 @@ js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder) if (!EnsureWorkerThreadsInitialized(cx)) return false; - WorkerThreadState &state = *cx->runtime()->workerThreadState; - JS_ASSERT(state.numThreads); + AutoLockWorkerThreadState lock; - AutoLockWorkerThreadState lock(state); - - if (!state.ionWorklist.append(builder)) + if (!WorkerThreadState().ionWorklist().append(builder)) return false; cx->runtime()->addCompilationThread(); - state.notifyAll(WorkerThreadState::PRODUCER); + WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER); return true; } /* * Move an IonBuilder for which compilation has either finished, failed, or - * been cancelled into the Ion compartment's finished compilations list. - * All off thread compilations which are started must eventually be finished. + * been cancelled into the global finished compilation list. All off thread + * compilations which are started must eventually be finished. */ static void FinishOffThreadIonCompile(jit::IonBuilder *builder) { - JSCompartment *compartment = builder->script()->compartment(); - JS_ASSERT(compartment->runtimeFromAnyThread()->workerThreadState); - JS_ASSERT(compartment->runtimeFromAnyThread()->workerThreadState->isLocked()); - - compartment->jitCompartment()->finishedOffThreadCompilations().append(builder); + WorkerThreadState().ionFinishedList().append(builder); } #endif // JS_ION @@ -130,49 +124,43 @@ void js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script) { #ifdef JS_ION - JSRuntime *rt = compartment->runtimeFromMainThread(); - - if (!rt->workerThreadState) - return; - - WorkerThreadState &state = *rt->workerThreadState; - jit::JitCompartment *jitComp = compartment->jitCompartment(); if (!jitComp) return; - AutoLockWorkerThreadState lock(state); + AutoLockWorkerThreadState lock; + + if (!WorkerThreadState().threads) + return; /* Cancel any pending entries for which processing hasn't started. */ - for (size_t i = 0; i < state.ionWorklist.length(); i++) { - jit::IonBuilder *builder = state.ionWorklist[i]; + GlobalWorkerThreadState::IonBuilderVector &worklist = WorkerThreadState().ionWorklist(); + for (size_t i = 0; i < worklist.length(); i++) { + jit::IonBuilder *builder = worklist[i]; if (CompiledScriptMatches(compartment, script, builder->script())) { FinishOffThreadIonCompile(builder); - state.ionWorklist[i--] = state.ionWorklist.back(); - state.ionWorklist.popBack(); + WorkerThreadState().remove(worklist, &i); } } /* Wait for in progress entries to finish up. */ - for (size_t i = 0; i < state.numThreads; i++) { - const WorkerThread &helper = state.threads[i]; + for (size_t i = 0; i < WorkerThreadState().threadCount; i++) { + const WorkerThread &helper = WorkerThreadState().threads[i]; while (helper.ionBuilder && CompiledScriptMatches(compartment, script, helper.ionBuilder->script())) { helper.ionBuilder->cancel(); - state.wait(WorkerThreadState::CONSUMER); + WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER); } } - jit::OffThreadCompilationVector &compilations = jitComp->finishedOffThreadCompilations(); - /* Cancel code generation for any completed entries. */ - for (size_t i = 0; i < compilations.length(); i++) { - jit::IonBuilder *builder = compilations[i]; + GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList(); + for (size_t i = 0; i < finished.length(); i++) { + jit::IonBuilder *builder = finished[i]; if (CompiledScriptMatches(compartment, script, builder->script())) { jit::FinishOffThreadBuilder(builder); - compilations[i--] = compilations.back(); - compilations.popBack(); + WorkerThreadState().remove(finished, &i); } } #endif // JS_ION @@ -237,6 +225,55 @@ ParseTask::~ParseTask() js_delete(errors[i]); } +void +js::CancelOffThreadParses(JSRuntime *rt) +{ + AutoLockWorkerThreadState lock; + + if (!WorkerThreadState().threads) + return; + + // Instead of forcibly canceling pending parse tasks, just wait for all scheduled + // and in progress ones to complete. Otherwise the final GC may not collect + // everything due to zones being used off thread. + while (true) { + bool pending = false; + GlobalWorkerThreadState::ParseTaskVector &worklist = WorkerThreadState().parseWorklist(); + for (size_t i = 0; i < worklist.length(); i++) { + ParseTask *task = worklist[i]; + if (task->runtimeMatches(rt)) + pending = true; + } + if (!pending) { + bool inProgress = false; + for (size_t i = 0; i < WorkerThreadState().threadCount; i++) { + ParseTask *task = WorkerThreadState().threads[i].parseTask; + if (task && task->runtimeMatches(rt)) + inProgress = true; + } + if (!inProgress) + break; + } + WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER); + } + + // Clean up any parse tasks which haven't been finished by the main thread. + GlobalWorkerThreadState::ParseTaskVector &finished = WorkerThreadState().parseFinishedList(); + while (true) { + bool found = false; + for (size_t i = 0; i < finished.length(); i++) { + ParseTask *task = finished[i]; + if (task->runtimeMatches(rt)) { + found = true; + AutoUnlockWorkerThreadState unlock; + WorkerThreadState().finishParseTask(/* maybecx = */ nullptr, rt, task); + } + } + if (!found) + break; + } +} + bool js::OffThreadParsingMustWaitForGC(JSRuntime *rt) { @@ -316,21 +353,19 @@ js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &optio if (!task->init(cx, options)) return false; - WorkerThreadState &state = *cx->runtime()->workerThreadState; - JS_ASSERT(state.numThreads); - if (OffThreadParsingMustWaitForGC(cx->runtime())) { - if (!state.parseWaitingOnGC.append(task.get())) + AutoLockWorkerThreadState lock; + if (!WorkerThreadState().parseWaitingOnGC().append(task.get())) return false; } else { task->activate(cx->runtime()); - AutoLockWorkerThreadState lock(state); + AutoLockWorkerThreadState lock; - if (!state.parseWorklist.append(task.get())) + if (!WorkerThreadState().parseWorklist().append(task.get())) return false; - state.notifyAll(WorkerThreadState::PRODUCER); + WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER); } task.forget(); @@ -343,45 +378,35 @@ js::EnqueuePendingParseTasksAfterGC(JSRuntime *rt) { JS_ASSERT(!OffThreadParsingMustWaitForGC(rt)); - if (!rt->workerThreadState || rt->workerThreadState->parseWaitingOnGC.empty()) + GlobalWorkerThreadState::ParseTaskVector newTasks; + { + AutoLockWorkerThreadState lock; + GlobalWorkerThreadState::ParseTaskVector &waiting = WorkerThreadState().parseWaitingOnGC(); + + for (size_t i = 0; i < waiting.length(); i++) { + ParseTask *task = waiting[i]; + if (task->runtimeMatches(rt)) { + newTasks.append(task); + WorkerThreadState().remove(waiting, &i); + } + } + } + + if (newTasks.empty()) return; // This logic should mirror the contents of the !activeGCInAtomsZone() // branch in StartOffThreadParseScript: - WorkerThreadState &state = *rt->workerThreadState; + for (size_t i = 0; i < newTasks.length(); i++) + newTasks[i]->activate(rt); - for (size_t i = 0; i < state.parseWaitingOnGC.length(); i++) - state.parseWaitingOnGC[i]->activate(rt); + AutoLockWorkerThreadState lock; - AutoLockWorkerThreadState lock(state); + for (size_t i = 0; i < newTasks.length(); i++) + WorkerThreadState().parseWorklist().append(newTasks[i]); - JS_ASSERT(state.parseWorklist.empty()); - state.parseWorklist.swap(state.parseWaitingOnGC); - - state.notifyAll(WorkerThreadState::PRODUCER); -} - -void -js::WaitForOffThreadParsingToFinish(JSRuntime *rt) -{ - if (!rt->workerThreadState) - return; - - WorkerThreadState &state = *rt->workerThreadState; - - AutoLockWorkerThreadState lock(state); - - while (true) { - if (state.parseWorklist.empty()) { - bool parseInProgress = false; - for (size_t i = 0; i < state.numThreads; i++) - parseInProgress |= !!state.threads[i].parseTask; - if (!parseInProgress) - break; - } - state.wait(WorkerThreadState::CONSUMER); - } + WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER); } #ifdef XP_WIN @@ -396,39 +421,26 @@ static const uint32_t WORKER_STACK_SIZE = 512 * 1024; static const uint32_t WORKER_STACK_QUOTA = 450 * 1024; bool -WorkerThreadState::init() +GlobalWorkerThreadState::ensureInitialized() { - JS_ASSERT(numThreads == 0); + JS_ASSERT(this == &WorkerThreadState()); + AutoLockWorkerThreadState lock; - if (!runtime->useHelperThreads()) + if (threads) return true; - workerLock = PR_NewLock(); - if (!workerLock) - return false; - - consumerWakeup = PR_NewCondVar(workerLock); - if (!consumerWakeup) - return false; - - producerWakeup = PR_NewCondVar(workerLock); - if (!producerWakeup) - return false; - - threads = (WorkerThread*) js_pod_calloc(runtime->workerThreadCount()); + threads = js_pod_calloc(threadCount); if (!threads) return false; - for (size_t i = 0; i < runtime->workerThreadCount(); i++) { + for (size_t i = 0; i < threadCount; i++) { WorkerThread &helper = threads[i]; - helper.runtime = runtime; - helper.threadData.construct(runtime); - helper.threadData.ref().addToThreadList(); + helper.threadData.construct(static_cast(nullptr)); helper.thread = PR_CreateThread(PR_USER_THREAD, WorkerThread::ThreadMain, &helper, PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, WORKER_STACK_SIZE); if (!helper.thread || !helper.threadData.ref().init()) { - for (size_t j = 0; j < runtime->workerThreadCount(); j++) + for (size_t j = 0; j < threadCount; j++) threads[j].destroy(); js_free(threads); threads = nullptr; @@ -436,50 +448,29 @@ WorkerThreadState::init() } } - numThreads = runtime->workerThreadCount(); resetAsmJSFailureState(); return true; } -void -WorkerThreadState::cleanup() +GlobalWorkerThreadState::GlobalWorkerThreadState() { - // Do preparatory work for shutdown before the final GC has destroyed most - // of the GC heap. + mozilla::PodZero(this); - // Join created threads, to ensure there is no in progress work. - if (threads) { - for (size_t i = 0; i < numThreads; i++) - threads[i].destroy(); - js_free(threads); - threads = nullptr; - numThreads = 0; - } + cpuCount = GetCPUCount(); + threadCount = ThreadCountForCPUCount(cpuCount); - // Clean up any parse tasks which haven't been finished yet. - while (!parseFinishedList.empty()) - finishParseTask(/* maybecx = */ nullptr, runtime, parseFinishedList[0]); -} + MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken"); -WorkerThreadState::~WorkerThreadState() -{ - JS_ASSERT(!threads); - JS_ASSERT(parseFinishedList.empty()); - - if (workerLock) - PR_DestroyLock(workerLock); - - if (consumerWakeup) - PR_DestroyCondVar(consumerWakeup); - - if (producerWakeup) - PR_DestroyCondVar(producerWakeup); + workerLock = PR_NewLock(); + consumerWakeup = PR_NewCondVar(workerLock); + producerWakeup = PR_NewCondVar(workerLock); } void -WorkerThreadState::lock() +GlobalWorkerThreadState::lock() { - runtime->assertCanLock(JSRuntime::WorkerThreadStateLock); + JS_ASSERT(!isLocked()); + AssertCurrentThreadCanLock(WorkerThreadStateLock); PR_Lock(workerLock); #ifdef DEBUG lockOwner = PR_GetCurrentThread(); @@ -487,7 +478,7 @@ WorkerThreadState::lock() } void -WorkerThreadState::unlock() +GlobalWorkerThreadState::unlock() { JS_ASSERT(isLocked()); #ifdef DEBUG @@ -498,14 +489,14 @@ WorkerThreadState::unlock() #ifdef DEBUG bool -WorkerThreadState::isLocked() +GlobalWorkerThreadState::isLocked() { return lockOwner == PR_GetCurrentThread(); } #endif void -WorkerThreadState::wait(CondVar which, uint32_t millis) +GlobalWorkerThreadState::wait(CondVar which, uint32_t millis) { JS_ASSERT(isLocked()); #ifdef DEBUG @@ -521,37 +512,37 @@ WorkerThreadState::wait(CondVar which, uint32_t millis) } void -WorkerThreadState::notifyAll(CondVar which) +GlobalWorkerThreadState::notifyAll(CondVar which) { JS_ASSERT(isLocked()); PR_NotifyAllCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup); } void -WorkerThreadState::notifyOne(CondVar which) +GlobalWorkerThreadState::notifyOne(CondVar which) { JS_ASSERT(isLocked()); PR_NotifyCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup); } bool -WorkerThreadState::canStartAsmJSCompile() +GlobalWorkerThreadState::canStartAsmJSCompile() { // Don't execute an AsmJS job if an earlier one failed. JS_ASSERT(isLocked()); - return (!asmJSWorklist.empty() && !numAsmJSFailedJobs); + return (!asmJSWorklist().empty() && !numAsmJSFailedJobs); } bool -WorkerThreadState::canStartIonCompile() +GlobalWorkerThreadState::canStartIonCompile() { // A worker thread can begin an Ion compilation if (a) there is some script // which is waiting to be compiled, and (b) no other worker thread is // currently compiling a script. The latter condition ensures that two // compilations cannot simultaneously occur. - if (ionWorklist.empty()) + if (ionWorklist().empty()) return false; - for (size_t i = 0; i < numThreads; i++) { + for (size_t i = 0; i < threadCount; i++) { if (threads[i].ionBuilder) return false; } @@ -559,16 +550,16 @@ WorkerThreadState::canStartIonCompile() } bool -WorkerThreadState::canStartParseTask() +GlobalWorkerThreadState::canStartParseTask() { // Don't allow simultaneous off thread parses, to reduce contention on the // atoms table. Note that asm.js compilation depends on this to avoid // stalling the worker thread, as off thread parse tasks can trigger and // block on other off thread asm.js compilation tasks. JS_ASSERT(isLocked()); - if (parseWorklist.empty()) + if (parseWorklist().empty()) return false; - for (size_t i = 0; i < numThreads; i++) { + for (size_t i = 0; i < threadCount; i++) { if (threads[i].parseTask) return false; } @@ -576,9 +567,9 @@ WorkerThreadState::canStartParseTask() } bool -WorkerThreadState::canStartCompressionTask() +GlobalWorkerThreadState::canStartCompressionTask() { - return !compressionWorklist.empty(); + return !compressionWorklist().empty(); } static void @@ -609,19 +600,19 @@ CallNewScriptHookForAllScripts(JSContext *cx, HandleScript script) } JSScript * -WorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token) +GlobalWorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token) { ParseTask *parseTask = nullptr; // The token is a ParseTask* which should be in the finished list. // Find and remove its entry. { - AutoLockWorkerThreadState lock(*rt->workerThreadState); - for (size_t i = 0; i < parseFinishedList.length(); i++) { - if (parseFinishedList[i] == token) { - parseTask = parseFinishedList[i]; - parseFinishedList[i] = parseFinishedList.back(); - parseFinishedList.popBack(); + AutoLockWorkerThreadState lock; + ParseTaskVector &finished = parseFinishedList(); + for (size_t i = 0; i < finished.length(); i++) { + if (finished[i] == token) { + parseTask = finished[i]; + remove(finished, &i); break; } } @@ -691,24 +682,20 @@ WorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *toke void WorkerThread::destroy() { - WorkerThreadState &state = *runtime->workerThreadState; - if (thread) { { - AutoLockWorkerThreadState lock(state); + AutoLockWorkerThreadState lock; terminate = true; /* Notify all workers, to ensure that this thread wakes up. */ - state.notifyAll(WorkerThreadState::PRODUCER); + WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER); } PR_JoinThread(thread); } - if (!threadData.empty()) { - threadData.ref().removeFromThreadList(); + if (!threadData.empty()) threadData.destroy(); - } } /* static */ @@ -720,19 +707,23 @@ WorkerThread::ThreadMain(void *arg) } void -WorkerThread::handleAsmJSWorkload(WorkerThreadState &state) +WorkerThread::handleAsmJSWorkload() { #ifdef JS_ION - JS_ASSERT(state.isLocked()); - JS_ASSERT(state.canStartAsmJSCompile()); + JS_ASSERT(WorkerThreadState().isLocked()); + JS_ASSERT(WorkerThreadState().canStartAsmJSCompile()); JS_ASSERT(idle()); - asmData = state.asmJSWorklist.popCopy(); + asmData = WorkerThreadState().asmJSWorklist().popCopy(); bool success = false; - state.unlock(); do { - jit::IonContext icx(jit::CompileRuntime::get(runtime), asmData->mir->compartment, &asmData->mir->alloc()); + AutoUnlockWorkerThreadState unlock; + PerThreadData::AutoEnterRuntime enter(threadData.addr(), asmData->runtime); + + jit::IonContext icx(asmData->mir->compartment->runtime(), + asmData->mir->compartment, + &asmData->mir->alloc()); int64_t before = PRMJ_Now(); @@ -748,38 +739,35 @@ WorkerThread::handleAsmJSWorkload(WorkerThreadState &state) success = true; } while(0); - state.lock(); // On failure, signal parent for harvesting in CancelOutstandingJobs(). if (!success) { - state.noteAsmJSFailure(asmData->func); - state.notifyAll(WorkerThreadState::CONSUMER); + WorkerThreadState().noteAsmJSFailure(asmData->func); + WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER); asmData = nullptr; return; } // On success, move work to the finished list. - state.asmJSFinishedList.append(asmData); + WorkerThreadState().asmJSFinishedList().append(asmData); asmData = nullptr; // Notify the main thread in case it's blocked waiting for a LifoAlloc. - state.notifyAll(WorkerThreadState::CONSUMER); + WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER); #else MOZ_CRASH(); #endif // JS_ION } void -WorkerThread::handleIonWorkload(WorkerThreadState &state) +WorkerThread::handleIonWorkload() { #ifdef JS_ION - JS_ASSERT(state.isLocked()); - JS_ASSERT(state.canStartIonCompile()); + JS_ASSERT(WorkerThreadState().isLocked()); + JS_ASSERT(WorkerThreadState().canStartIonCompile()); JS_ASSERT(idle()); - ionBuilder = state.ionWorklist.popCopy(); - - DebugOnly executionMode = ionBuilder->info().executionMode(); + ionBuilder = WorkerThreadState().ionWorklist().popCopy(); #if JS_TRACE_LOGGING AutoTraceLog logger(TraceLogging::getLogger(TraceLogging::ION_BACKGROUND_COMPILER), @@ -788,9 +776,13 @@ WorkerThread::handleIonWorkload(WorkerThreadState &state) ionBuilder->script()); #endif - state.unlock(); + JSRuntime *rt = ionBuilder->script()->compartment()->runtimeFromAnyThread(); + { - jit::IonContext ictx(jit::CompileRuntime::get(runtime), + AutoUnlockWorkerThreadState unlock; + PerThreadData::AutoEnterRuntime enter(threadData.addr(), + ionBuilder->script()->runtimeFromAnyThread()); + jit::IonContext ictx(jit::CompileRuntime::get(rt), jit::CompileCompartment::get(ionBuilder->script()->compartment()), &ionBuilder->alloc()); AutoEnterIonCompilation ionCompiling; @@ -799,7 +791,6 @@ WorkerThread::handleIonWorkload(WorkerThreadState &state) if (succeeded) ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder)); } - state.lock(); FinishOffThreadIonCompile(ionBuilder); ionBuilder = nullptr; @@ -808,10 +799,10 @@ WorkerThread::handleIonWorkload(WorkerThreadState &state) // at the next operation callback. Don't interrupt Ion code for this, as // this incorporation can be delayed indefinitely without affecting // performance as long as the main thread is actually executing Ion code. - runtime->triggerOperationCallback(JSRuntime::TriggerCallbackAnyThreadDontStopIon); + rt->triggerOperationCallback(JSRuntime::TriggerCallbackAnyThreadDontStopIon); // Notify the main thread in case it is waiting for the compilation to finish. - state.notifyAll(WorkerThreadState::CONSUMER); + WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER); #else MOZ_CRASH(); #endif // JS_ION @@ -843,17 +834,19 @@ ExclusiveContext::addPendingOverRecursed() } void -WorkerThread::handleParseWorkload(WorkerThreadState &state) +WorkerThread::handleParseWorkload() { - JS_ASSERT(state.isLocked()); - JS_ASSERT(state.canStartParseTask()); + JS_ASSERT(WorkerThreadState().isLocked()); + JS_ASSERT(WorkerThreadState().canStartParseTask()); JS_ASSERT(idle()); - parseTask = state.parseWorklist.popCopy(); + parseTask = WorkerThreadState().parseWorklist().popCopy(); parseTask->cx->setWorkerThread(this); { - AutoUnlockWorkerThreadState unlock(runtime); + AutoUnlockWorkerThreadState unlock; + PerThreadData::AutoEnterRuntime enter(threadData.addr(), + parseTask->exclusiveContextGlobal->runtimeFromAnyThread()); parseTask->script = frontend::CompileScript(parseTask->cx, &parseTask->alloc, NullPtr(), NullPtr(), parseTask->options, @@ -865,26 +858,26 @@ WorkerThread::handleParseWorkload(WorkerThreadState &state) // FinishOffThreadScript will need to be called on the script to // migrate it into the correct compartment. - state.parseFinishedList.append(parseTask); + WorkerThreadState().parseFinishedList().append(parseTask); parseTask = nullptr; // Notify the main thread in case it is waiting for the parse/emit to finish. - state.notifyAll(WorkerThreadState::CONSUMER); + WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER); } void -WorkerThread::handleCompressionWorkload(WorkerThreadState &state) +WorkerThread::handleCompressionWorkload() { - JS_ASSERT(state.isLocked()); - JS_ASSERT(state.canStartCompressionTask()); + JS_ASSERT(WorkerThreadState().isLocked()); + JS_ASSERT(WorkerThreadState().canStartCompressionTask()); JS_ASSERT(idle()); - compressionTask = state.compressionWorklist.popCopy(); + compressionTask = WorkerThreadState().compressionWorklist().popCopy(); compressionTask->workerThread = this; { - AutoUnlockWorkerThreadState unlock(runtime); + AutoUnlockWorkerThreadState unlock; if (!compressionTask->work()) compressionTask->setOOM(); } @@ -893,7 +886,7 @@ WorkerThread::handleCompressionWorkload(WorkerThreadState &state) compressionTask = nullptr; // Notify the main thread in case it is waiting for the compression to finish. - state.notifyAll(WorkerThreadState::CONSUMER); + WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER); } bool @@ -902,25 +895,24 @@ js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task) if (!EnsureWorkerThreadsInitialized(cx)) return false; - WorkerThreadState &state = *cx->workerThreadState(); - AutoLockWorkerThreadState lock(state); + AutoLockWorkerThreadState lock; - if (!state.compressionWorklist.append(task)) + if (!WorkerThreadState().compressionWorklist().append(task)) return false; - state.notifyAll(WorkerThreadState::PRODUCER); + WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER); return true; } bool -WorkerThreadState::compressionInProgress(SourceCompressionTask *task) +GlobalWorkerThreadState::compressionInProgress(SourceCompressionTask *task) { JS_ASSERT(isLocked()); - for (size_t i = 0; i < compressionWorklist.length(); i++) { - if (compressionWorklist[i] == task) + for (size_t i = 0; i < compressionWorklist().length(); i++) { + if (compressionWorklist()[i] == task) return true; } - for (size_t i = 0; i < numThreads; i++) { + for (size_t i = 0; i < threadCount; i++) { if (threads[i].compressionTask == task) return true; } @@ -932,11 +924,10 @@ SourceCompressionTask::complete() { JS_ASSERT_IF(!ss, !chars); if (active()) { - WorkerThreadState &state = *cx->workerThreadState(); - AutoLockWorkerThreadState lock(state); + AutoLockWorkerThreadState lock; - while (state.compressionInProgress(this)) - state.wait(WorkerThreadState::CONSUMER); + while (WorkerThreadState().compressionInProgress(this)) + WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER); ss->ready_ = true; @@ -955,15 +946,15 @@ SourceCompressionTask::complete() } SourceCompressionTask * -WorkerThreadState::compressionTaskForSource(ScriptSource *ss) +GlobalWorkerThreadState::compressionTaskForSource(ScriptSource *ss) { JS_ASSERT(isLocked()); - for (size_t i = 0; i < compressionWorklist.length(); i++) { - SourceCompressionTask *task = compressionWorklist[i]; + for (size_t i = 0; i < compressionWorklist().length(); i++) { + SourceCompressionTask *task = compressionWorklist()[i]; if (task->source() == ss) return task; } - for (size_t i = 0; i < numThreads; i++) { + for (size_t i = 0; i < threadCount; i++) { SourceCompressionTask *task = threads[i].compressionTask; if (task && task->source() == ss) return task; @@ -981,12 +972,11 @@ ScriptSource::getOffThreadCompressionChars(ExclusiveContext *cx) return nullptr; } - WorkerThreadState &state = *cx->workerThreadState(); - AutoLockWorkerThreadState lock(state); + AutoLockWorkerThreadState lock; // Look for a token that hasn't finished compressing and whose source is // the given ScriptSource. - if (SourceCompressionTask *task = state.compressionTaskForSource(this)) + if (SourceCompressionTask *task = WorkerThreadState().compressionTaskForSource(this)) return task->uncompressedChars(); // Compressing has finished, so this ScriptSource is ready. Avoid future @@ -1000,8 +990,7 @@ void WorkerThread::threadLoop() { JS::AutoAssertNoGC nogc; - WorkerThreadState &state = *runtime->workerThreadState; - AutoLockWorkerThreadState lock(state); + AutoLockWorkerThreadState lock; js::TlsPerThreadData.set(threadData.addr()); @@ -1022,25 +1011,25 @@ WorkerThread::threadLoop() while (true) { if (terminate) return; - if (state.canStartIonCompile() || - state.canStartAsmJSCompile() || - state.canStartParseTask() || - state.canStartCompressionTask()) + if (WorkerThreadState().canStartIonCompile() || + WorkerThreadState().canStartAsmJSCompile() || + WorkerThreadState().canStartParseTask() || + WorkerThreadState().canStartCompressionTask()) { break; } - state.wait(WorkerThreadState::PRODUCER); + WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER); } // Dispatch tasks, prioritizing AsmJS work. - if (state.canStartAsmJSCompile()) - handleAsmJSWorkload(state); - else if (state.canStartIonCompile()) - handleIonWorkload(state); - else if (state.canStartParseTask()) - handleParseWorkload(state); - else if (state.canStartCompressionTask()) - handleCompressionWorkload(state); + if (WorkerThreadState().canStartAsmJSCompile()) + handleAsmJSWorkload(); + else if (WorkerThreadState().canStartIonCompile()) + handleIonWorkload(); + else if (WorkerThreadState().canStartParseTask()) + handleParseWorkload(); + else if (WorkerThreadState().canStartCompressionTask()) + handleCompressionWorkload(); else MOZ_ASSUME_UNREACHABLE("No task to perform"); } @@ -1071,6 +1060,11 @@ js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script) { } +void +js::CancelOffThreadParses(JSRuntime *rt) +{ +} + bool js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options, const jschar *chars, size_t length, HandleObject scopeChain, @@ -1079,11 +1073,6 @@ js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &optio MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds"); } -void -js::WaitForOffThreadParsingToFinish(JSRuntime *rt) -{ -} - bool js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task) { diff --git a/js/src/jsworkers.h b/js/src/jsworkers.h index 02ee1f0333c..9f417029149 100644 --- a/js/src/jsworkers.h +++ b/js/src/jsworkers.h @@ -33,59 +33,58 @@ namespace jit { #ifdef JS_THREADSAFE -/* Per-runtime state for off thread work items. */ -class WorkerThreadState +// Per-process state for off thread work items. +class GlobalWorkerThreadState { public: - /* Available threads. */ + // Number of CPUs to treat this machine as having when creating threads. + // May be accessed without locking. + size_t cpuCount; + + // Number of threads to create. May be accessed without locking. + size_t threadCount; + + typedef Vector IonBuilderVector; + typedef Vector AsmJSParallelTaskVector; + typedef Vector ParseTaskVector; + typedef Vector SourceCompressionTaskVector; + + // List of available threads, or null if the thread state has not been initialized. WorkerThread *threads; - size_t numThreads; - enum CondVar { - /* For notifying threads waiting for work that they may be able to make progress. */ - CONSUMER, + private: + // The lists below are all protected by |lock|. - /* For notifying threads doing work that they may be able to make progress. */ - PRODUCER - }; + // Ion compilation worklist and finished jobs. + IonBuilderVector ionWorklist_, ionFinishedList_; - /* Shared worklist for Ion worker threads. */ - Vector ionWorklist; + // AsmJS worklist and finished jobs. + // + // Simultaneous AsmJS compilations all service the same AsmJS module. + // The main thread must pick up finished optimizations and perform codegen. + // |asmJSCompilationInProgress| is used to avoid triggering compilations + // for more than one module at a time. + AsmJSParallelTaskVector asmJSWorklist_, asmJSFinishedList_; - /* Worklist for AsmJS worker threads. */ - Vector asmJSWorklist; - - /* - * Finished list for AsmJS worker threads. - * Simultaneous AsmJS compilations all service the same AsmJS module. - * The main thread must pick up finished optimizations and perform codegen. - */ - Vector asmJSFinishedList; - - /* - * For now, only allow a single parallel asm.js compilation to happen at a - * time. This avoids race conditions on asmJSWorklist/asmJSFinishedList/etc. - */ + public: + // For now, only allow a single parallel asm.js compilation to happen at a + // time. This avoids race conditions on asmJSWorklist/asmJSFinishedList/etc. mozilla::Atomic asmJSCompilationInProgress; - /* Shared worklist for parsing/emitting scripts on worker threads. */ - Vector parseWorklist, parseFinishedList; + private: + // Script parsing/emitting worklist and finished jobs. + ParseTaskVector parseWorklist_, parseFinishedList_; - /* Main-thread-only list of parse tasks waiting for an atoms-zone GC to complete. */ - Vector parseWaitingOnGC; + // Parse tasks waiting for an atoms-zone GC to complete. + ParseTaskVector parseWaitingOnGC_; - /* Worklist for source compression worker threads. */ - Vector compressionWorklist; + // Source compression worklist. + SourceCompressionTaskVector compressionWorklist_; - WorkerThreadState(JSRuntime *rt) { - mozilla::PodZero(this); - runtime = rt; - } - ~WorkerThreadState(); - - bool init(); - void cleanup(); + public: + GlobalWorkerThreadState(); + bool ensureInitialized(); void lock(); void unlock(); @@ -93,10 +92,62 @@ class WorkerThreadState bool isLocked(); # endif + enum CondVar { + // For notifying threads waiting for work that they may be able to make progress. + CONSUMER, + + // For notifying threads doing work that they may be able to make progress. + PRODUCER + }; + void wait(CondVar which, uint32_t timeoutMillis = 0); void notifyAll(CondVar which); void notifyOne(CondVar which); + // Helper method for removing items from the vectors below while iterating over them. + template + void remove(T &vector, size_t *index) + { + vector[(*index)--] = vector.back(); + vector.popBack(); + } + + IonBuilderVector &ionWorklist() { + JS_ASSERT(isLocked()); + return ionWorklist_; + } + IonBuilderVector &ionFinishedList() { + JS_ASSERT(isLocked()); + return ionFinishedList_; + } + + AsmJSParallelTaskVector &asmJSWorklist() { + JS_ASSERT(isLocked()); + return asmJSWorklist_; + } + AsmJSParallelTaskVector &asmJSFinishedList() { + JS_ASSERT(isLocked()); + return asmJSFinishedList_; + } + + ParseTaskVector &parseWorklist() { + JS_ASSERT(isLocked()); + return parseWorklist_; + } + ParseTaskVector &parseFinishedList() { + JS_ASSERT(isLocked()); + return parseFinishedList_; + } + ParseTaskVector &parseWaitingOnGC() { + JS_ASSERT(isLocked()); + return parseWaitingOnGC_; + } + + SourceCompressionTaskVector &compressionWorklist() { + JS_ASSERT(isLocked()); + return compressionWorklist_; + } + bool canStartAsmJSCompile(); bool canStartIonCompile(); bool canStartParseTask(); @@ -132,8 +183,6 @@ class WorkerThreadState private: - JSRuntime *runtime; - /* * Lock protecting all mutable shared state accessed by helper threads, and * used by all condition variables. @@ -161,11 +210,16 @@ class WorkerThreadState void *asmJSFailedFunction; }; +static inline GlobalWorkerThreadState & +WorkerThreadState() +{ + extern GlobalWorkerThreadState gWorkerThreadState; + return gWorkerThreadState; +} + /* Individual helper thread, one allocated per core. */ struct WorkerThread { - JSRuntime *runtime; - mozilla::Maybe threadData; PRThread *thread; @@ -190,10 +244,10 @@ struct WorkerThread void destroy(); - void handleAsmJSWorkload(WorkerThreadState &state); - void handleIonWorkload(WorkerThreadState &state); - void handleParseWorkload(WorkerThreadState &state); - void handleCompressionWorkload(WorkerThreadState &state); + void handleAsmJSWorkload(); + void handleIonWorkload(); + void handleParseWorkload(); + void handleCompressionWorkload(); static void ThreadMain(void *arg); void threadLoop(); @@ -203,10 +257,15 @@ struct WorkerThread /* Methods for interacting with worker threads. */ -/* Initialize worker threads unless already initialized. */ +// Initialize worker threads unless already initialized. bool EnsureWorkerThreadsInitialized(ExclusiveContext *cx); +// This allows the JS shell to override GetCPUCount() when passed the +// --thread-count=N option. +void +SetFakeCPUCount(size_t count); + #ifdef JS_ION /* Perform MIR optimization and LIR generation on a single function. */ @@ -229,6 +288,10 @@ StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder); void CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script); +/* Cancel all scheduled, in progress or finished parses for runtime. */ +void +CancelOffThreadParses(JSRuntime *runtime); + /* * Start a parse/emit cycle for a stream of source. The characters must stay * alive until the compilation finishes. @@ -245,10 +308,6 @@ StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options, void EnqueuePendingParseTasksAfterGC(JSRuntime *rt); -/* Block until in progress and pending off thread parse jobs have finished. */ -void -WaitForOffThreadParsingToFinish(JSRuntime *rt); - /* Start a compression job for the specified token. */ bool StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task); @@ -258,24 +317,19 @@ class AutoLockWorkerThreadState MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER #ifdef JS_THREADSAFE - WorkerThreadState &state; - public: - AutoLockWorkerThreadState(WorkerThreadState &state - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) - : state(state) + AutoLockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; - state.lock(); + WorkerThreadState().lock(); } ~AutoLockWorkerThreadState() { - state.unlock(); + WorkerThreadState().unlock(); } #else public: - AutoLockWorkerThreadState(WorkerThreadState &state - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + AutoLockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } @@ -284,28 +338,22 @@ class AutoLockWorkerThreadState class AutoUnlockWorkerThreadState { - JSRuntime *rt; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER public: - AutoUnlockWorkerThreadState(JSRuntime *rt - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) - : rt(rt) + AutoUnlockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; #ifdef JS_THREADSAFE - JS_ASSERT(rt->workerThreadState); - rt->workerThreadState->unlock(); -#else - (void)this->rt; + WorkerThreadState().unlock(); #endif } ~AutoUnlockWorkerThreadState() { #ifdef JS_THREADSAFE - rt->workerThreadState->lock(); + WorkerThreadState().lock(); #endif } }; @@ -313,6 +361,7 @@ class AutoUnlockWorkerThreadState #ifdef JS_ION struct AsmJSParallelTask { + JSRuntime *runtime; // Associated runtime. LifoAlloc lifo; // Provider of all heap memory used for compilation. void *func; // Really, a ModuleCompiler::Func* jit::MIRGenerator *mir; // Passed from main thread to worker. @@ -320,10 +369,11 @@ struct AsmJSParallelTask unsigned compileTime; AsmJSParallelTask(size_t defaultChunkSize) - : lifo(defaultChunkSize), func(nullptr), mir(nullptr), lir(nullptr), compileTime(0) + : runtime(nullptr), lifo(defaultChunkSize), func(nullptr), mir(nullptr), lir(nullptr), compileTime(0) { } - void init(void *func, jit::MIRGenerator *mir) { + void init(JSRuntime *rt, void *func, jit::MIRGenerator *mir) { + this->runtime = rt; this->func = func; this->mir = mir; this->lir = nullptr; @@ -376,6 +426,10 @@ struct ParseTask void activate(JSRuntime *rt); void finish(); + bool runtimeMatches(JSRuntime *rt) { + return exclusiveContextGlobal->runtimeFromAnyThread() == rt; + } + ~ParseTask(); }; diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 9eb3431a6fb..2875c2adffc 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -5456,7 +5456,7 @@ ProcessArgs(JSContext *cx, JSObject *obj_, OptionParser *op) #ifdef JS_THREADSAFE int32_t threadCount = op->getIntOption("thread-count"); if (threadCount >= 0) - cx->runtime()->setFakeCPUCount(threadCount); + SetFakeCPUCount(threadCount); #endif /* JS_THREADSAFE */ #if defined(JS_ION) diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 8a4f835593c..8efeb675772 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -91,9 +91,6 @@ PerThreadData::~PerThreadData() if (dtoaState) js_DestroyDtoaState(dtoaState); - if (isInList()) - removeFromThreadList(); - #ifdef JS_ARM_SIMULATOR js_delete(simulator_); #endif @@ -109,22 +106,6 @@ PerThreadData::init() return true; } -void -PerThreadData::addToThreadList() -{ - // PerThreadData which are created/destroyed off the main thread do not - // show up in the runtime's thread list. - JS_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); - runtime_->threadList.insertBack(this); -} - -void -PerThreadData::removeFromThreadList() -{ - JS_ASSERT(CurrentThreadCanAccessRuntime(runtime_)); - removeFrom(runtime_->threadList); -} - static const JSWrapObjectCallbacks DefaultWrapObjectCallbacks = { TransparentObjectWrapper, nullptr, @@ -144,7 +125,6 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads) #ifdef JS_THREADSAFE operationCallbackLock(nullptr), operationCallbackOwner(nullptr), - workerThreadState(nullptr), exclusiveAccessLock(nullptr), exclusiveAccessOwner(nullptr), mainThreadHasExclusiveAccess(false), @@ -316,11 +296,6 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads) parallelWarmup(0), ionReturnOverride_(MagicValue(JS_ARG_POISON)), useHelperThreads_(useHelperThreads), -#ifdef JS_THREADSAFE - cpuCount_(GetCPUCount()), -#else - cpuCount_(1), -#endif parallelIonCompilationEnabled_(true), parallelParsingEnabled_(true), isWorkerRuntime_(false) @@ -328,8 +303,6 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads) , enteredPolicy(nullptr) #endif { - MOZ_ASSERT(cpuCount_ > 0, "GetCPUCount() seems broken"); - liveRuntimesCount++; setGCMode(JSGC_MODE_GLOBAL); @@ -392,7 +365,6 @@ JSRuntime::init(uint32_t maxbytes) return false; js::TlsPerThreadData.set(&mainThread); - mainThread.addToThreadList(); if (!threadPool.init()) return false; @@ -463,15 +435,15 @@ JSRuntime::~JSRuntime() /* Free source hook early, as its destructor may want to delete roots. */ sourceHook = nullptr; - /* Off thread compilation and parsing depend on atoms still existing. */ + /* + * Cancel any pending, in progress or completed Ion compilations and + * parse tasks. Waiting for AsmJS and compression tasks is done + * synchronously (on the main thread or during parse tasks), so no + * explicit canceling is needed for these. + */ for (CompartmentsIter comp(this, SkipAtoms); !comp.done(); comp.next()) CancelOffThreadIonCompile(comp, nullptr); - WaitForOffThreadParsingToFinish(this); - -#ifdef JS_THREADSAFE - if (workerThreadState) - workerThreadState->cleanup(); -#endif + CancelOffThreadParses(this); /* Poison common names before final GC. */ FinishCommonNames(this); @@ -504,11 +476,7 @@ JSRuntime::~JSRuntime() */ finishSelfHosting(); - mainThread.removeFromThreadList(); - #ifdef JS_THREADSAFE - js_delete(workerThreadState); - JS_ASSERT(!exclusiveAccessOwner); if (exclusiveAccessLock) PR_DestroyLock(exclusiveAccessLock); @@ -1010,7 +978,7 @@ JSRuntime::assertCanLock(RuntimeLock which) case ExclusiveAccessLock: JS_ASSERT(exclusiveAccessOwner != PR_GetCurrentThread()); case WorkerThreadStateLock: - JS_ASSERT_IF(workerThreadState, !workerThreadState->isLocked()); + JS_ASSERT(!WorkerThreadState().isLocked()); case CompilationLock: JS_ASSERT(compilationLockOwner != PR_GetCurrentThread()); case OperationCallbackLock: @@ -1082,4 +1050,14 @@ js::CurrentThreadCanReadCompilationData() #endif } +void +js::AssertCurrentThreadCanLock(RuntimeLock which) +{ +#ifdef JS_THREADSAFE + PerThreadData *pt = TlsPerThreadData.get(); + if (pt && pt->runtime_) + pt->runtime_->assertCanLock(which); +#endif +} + #endif // DEBUG diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 16ba1558290..7681b536781 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -84,7 +84,6 @@ class Activation; class ActivationIterator; class AsmJSActivation; class MathCache; -class WorkerThreadState; namespace jit { class JitRuntime; @@ -477,15 +476,31 @@ AtomStateOffsetToName(const JSAtomState &atomState, size_t offset) return *(js::FixedHeapPtr*)((char*)&atomState + offset); } +// There are several coarse locks in the enum below. These may be either +// per-runtime or per-process. When acquiring more than one of these locks, +// the acquisition must be done in the order below to avoid deadlocks. +enum RuntimeLock { + ExclusiveAccessLock, + WorkerThreadStateLock, + CompilationLock, + OperationCallbackLock, + GCLock +}; + +#ifdef DEBUG +void AssertCurrentThreadCanLock(RuntimeLock which); +#else +inline void AssertCurrentThreadCanLock(RuntimeLock which) {} +#endif + /* * Encapsulates portions of the runtime/context that are tied to a - * single active thread. Normally, as most JS is single-threaded, - * there is only one instance of this struct, embedded in the - * JSRuntime as the field |mainThread|. During Parallel JS sections, - * however, there will be one instance per worker thread. + * single active thread. Instances of this structure can occur for + * the main thread as |JSRuntime::mainThread|, for select operations + * performed off thread, such as parsing, and for Parallel JS worker + * threads. */ -class PerThreadData : public PerThreadDataFriendFields, - public mozilla::LinkedListElement +class PerThreadData : public PerThreadDataFriendFields { /* * Backpointer to the full shared JSRuntime* with which this @@ -538,6 +553,7 @@ class PerThreadData : public PerThreadDataFriendFields, friend class js::AsmJSActivation; #ifdef DEBUG friend bool js::CurrentThreadCanReadCompilationData(); + friend void js::AssertCurrentThreadCanLock(RuntimeLock which); #endif /* @@ -598,8 +614,6 @@ class PerThreadData : public PerThreadDataFriendFields, ~PerThreadData(); bool init(); - void addToThreadList(); - void removeFromThreadList(); bool associatedWith(const JSRuntime *rt) { return runtime_ == rt; } inline JSRuntime *runtimeFromMainThread(); @@ -609,6 +623,25 @@ class PerThreadData : public PerThreadDataFriendFields, inline void addActiveCompilation(); inline void removeActiveCompilation(); + // For threads which may be associated with different runtimes, depending + // on the work they are doing. + class AutoEnterRuntime + { + PerThreadData *pt; + + public: + AutoEnterRuntime(PerThreadData *pt, JSRuntime *rt) + : pt(pt) + { + JS_ASSERT(!pt->runtime_); + pt->runtime_ = rt; + } + + ~AutoEnterRuntime() { + pt->runtime_ = nullptr; + } + }; + #ifdef JS_ARM_SIMULATOR js::jit::Simulator *simulator() const; void setSimulator(js::jit::Simulator *sim); @@ -646,12 +679,6 @@ struct JSRuntime : public JS::shadow::Runtime, */ js::PerThreadData mainThread; - /* - * List of per-thread data in the runtime, including mainThread. Currently - * this does not include instances of PerThreadData created for PJS. - */ - mozilla::LinkedList threadList; - /* * If non-zero, we were been asked to call the operation callback as soon * as possible. @@ -668,20 +695,10 @@ struct JSRuntime : public JS::shadow::Runtime, /* Branch callback */ JSOperationCallback operationCallback; - // There are several per-runtime locks indicated by the enum below. When - // acquiring multiple of these locks, the acquisition must be done in the - // order below to avoid deadlocks. - enum RuntimeLock { - ExclusiveAccessLock, - WorkerThreadStateLock, - CompilationLock, - OperationCallbackLock, - GCLock - }; #ifdef DEBUG - void assertCanLock(RuntimeLock which); + void assertCanLock(js::RuntimeLock which); #else - void assertCanLock(RuntimeLock which) {} + void assertCanLock(js::RuntimeLock which) {} #endif private: @@ -702,7 +719,7 @@ struct JSRuntime : public JS::shadow::Runtime, public: AutoLockForOperationCallback(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : rt(rt) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; - rt->assertCanLock(JSRuntime::OperationCallbackLock); + rt->assertCanLock(js::OperationCallbackLock); #ifdef JS_THREADSAFE PR_Lock(rt->operationCallbackLock); rt->operationCallbackOwner = PR_GetCurrentThread(); @@ -733,8 +750,6 @@ struct JSRuntime : public JS::shadow::Runtime, #ifdef JS_THREADSAFE - js::WorkerThreadState *workerThreadState; - private: /* * Lock taken when using per-runtime or per-zone data that could otherwise @@ -1384,7 +1399,7 @@ struct JSRuntime : public JS::shadow::Runtime, void lockGC() { #ifdef JS_THREADSAFE - assertCanLock(GCLock); + assertCanLock(js::GCLock); PR_Lock(gcLock); JS_ASSERT(!gcLockOwner); #ifdef DEBUG @@ -1717,7 +1732,6 @@ struct JSRuntime : public JS::shadow::Runtime, private: JSUseHelperThreads useHelperThreads_; - unsigned cpuCount_; // Settings for how helper threads can be used. bool parallelIonCompilationEnabled_; @@ -1739,39 +1753,14 @@ struct JSRuntime : public JS::shadow::Runtime, #endif } - // This allows the JS shell to override GetCPUCount() when passed the - // --thread-count=N option. - void setFakeCPUCount(size_t count) { - cpuCount_ = count; - } - - // Return a cached value of GetCPUCount() to avoid making the syscall all - // the time. Furthermore, this avoids pathological cases where the result of - // GetCPUCount() changes during execution. - unsigned cpuCount() const { - JS_ASSERT(cpuCount_ > 0); - return cpuCount_; - } - - // The number of worker threads that will be available after - // EnsureWorkerThreadsInitialized has been called successfully. - unsigned workerThreadCount() const { - if (!useHelperThreads()) - return 0; - return js::Max(2u, cpuCount()); - } - // Note: these values may be toggled dynamically (in response to about:config // prefs changing). void setParallelIonCompilationEnabled(bool value) { parallelIonCompilationEnabled_ = value; } bool canUseParallelIonCompilation() const { - // Require cpuCount_ > 1 so that Ion compilation jobs and main-thread - // execution are not competing for the same resources. return useHelperThreads() && - parallelIonCompilationEnabled_ && - cpuCount_ > 1; + parallelIonCompilationEnabled_; } void setParallelParsingEnabled(bool value) { parallelParsingEnabled_ = value; diff --git a/js/src/vm/ThreadPool.cpp b/js/src/vm/ThreadPool.cpp index aa1faa23bfc..0534dfac687 100644 --- a/js/src/vm/ThreadPool.cpp +++ b/js/src/vm/ThreadPool.cpp @@ -392,7 +392,7 @@ uint32_t ThreadPool::numWorkers() const { // Subtract one for the main thread, which always exists. - return runtime_->cpuCount() - 1; + return WorkerThreadState().cpuCount - 1; } bool