Bug 941805 - Make the pool of JS workers be per process rather than per runtime, r=billm.

This commit is contained in:
Brian Hackett 2014-01-31 18:58:16 -07:00
parent 0da87bcc7d
commit 5e0f361bec
15 changed files with 536 additions and 523 deletions

View File

@ -1405,7 +1405,7 @@ static bool
WorkerThreadCount(JSContext *cx, unsigned argc, jsval *vp) WorkerThreadCount(JSContext *cx, unsigned argc, jsval *vp)
{ {
CallArgs args = CallArgsFromVp(argc, vp); CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setNumber(static_cast<double>(cx->runtime()->workerThreadCount())); args.rval().setInt32(cx->runtime()->useHelperThreads() ? WorkerThreadState().threadCount : 0);
return true; return true;
} }

View File

@ -5364,20 +5364,20 @@ CheckFunctionsSequential(ModuleCompiler &m)
// on rt->workerThreadState->asmJSCompilationInProgress. // on rt->workerThreadState->asmJSCompilationInProgress.
class ParallelCompilationGuard class ParallelCompilationGuard
{ {
WorkerThreadState *parallelState_; bool parallelState_;
public: public:
ParallelCompilationGuard() : parallelState_(nullptr) {} ParallelCompilationGuard() : parallelState_(false) {}
~ParallelCompilationGuard() { ~ParallelCompilationGuard() {
if (parallelState_) { if (parallelState_) {
JS_ASSERT(parallelState_->asmJSCompilationInProgress == true); JS_ASSERT(WorkerThreadState().asmJSCompilationInProgress == true);
parallelState_->asmJSCompilationInProgress = false; WorkerThreadState().asmJSCompilationInProgress = false;
} }
} }
bool claim(WorkerThreadState *state) { bool claim() {
JS_ASSERT(!parallelState_); JS_ASSERT(!parallelState_);
if (!state->asmJSCompilationInProgress.compareExchange(false, true)) if (!WorkerThreadState().asmJSCompilationInProgress.compareExchange(false, true))
return false; return false;
parallelState_ = state; parallelState_ = true;
return true; return true;
} }
}; };
@ -5391,22 +5391,23 @@ ParallelCompilationEnabled(ExclusiveContext *cx)
// parsing task, ensure that there another free thread to avoid deadlock. // 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 // (Note: there is at most one thread used for parsing so we don't have to
// worry about general dining philosophers.) // worry about general dining philosophers.)
if (!cx->isJSContext()) if (WorkerThreadState().threadCount <= 1)
return cx->workerThreadState()->numThreads > 1; return false;
if (!cx->isJSContext())
return true;
return cx->asJSContext()->runtime()->canUseParallelIonCompilation(); return cx->asJSContext()->runtime()->canUseParallelIonCompilation();
} }
// State of compilation as tracked and updated by the main thread. // State of compilation as tracked and updated by the main thread.
struct ParallelGroupState struct ParallelGroupState
{ {
WorkerThreadState &state;
js::Vector<AsmJSParallelTask> &tasks; js::Vector<AsmJSParallelTask> &tasks;
int32_t outstandingJobs; // Good work, jobs! int32_t outstandingJobs; // Good work, jobs!
uint32_t compiledJobs; uint32_t compiledJobs;
ParallelGroupState(WorkerThreadState &state, js::Vector<AsmJSParallelTask> &tasks) ParallelGroupState(js::Vector<AsmJSParallelTask> &tasks)
: state(state), tasks(tasks), outstandingJobs(0), compiledJobs(0) : tasks(tasks), outstandingJobs(0), compiledJobs(0)
{ } { }
}; };
@ -5414,14 +5415,14 @@ struct ParallelGroupState
static AsmJSParallelTask * static AsmJSParallelTask *
GetFinishedCompilation(ModuleCompiler &m, ParallelGroupState &group) GetFinishedCompilation(ModuleCompiler &m, ParallelGroupState &group)
{ {
AutoLockWorkerThreadState lock(*m.cx()->workerThreadState()); AutoLockWorkerThreadState lock;
while (!group.state.asmJSWorkerFailed()) { while (!WorkerThreadState().asmJSWorkerFailed()) {
if (!group.state.asmJSFinishedList.empty()) { if (!WorkerThreadState().asmJSFinishedList().empty()) {
group.outstandingJobs--; group.outstandingJobs--;
return group.state.asmJSFinishedList.popCopy(); return WorkerThreadState().asmJSFinishedList().popCopy();
} }
group.state.wait(WorkerThreadState::CONSUMER); WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
} }
return nullptr; return nullptr;
@ -5471,9 +5472,14 @@ GetUnusedTask(ParallelGroupState &group, uint32_t i, AsmJSParallelTask **outTask
static bool static bool
CheckFunctionsParallelImpl(ModuleCompiler &m, ParallelGroupState &group) CheckFunctionsParallelImpl(ModuleCompiler &m, ParallelGroupState &group)
{ {
JS_ASSERT(group.state.asmJSWorklist.empty()); #ifdef DEBUG
JS_ASSERT(group.state.asmJSFinishedList.empty()); {
group.state.resetAsmJSFailureState(); 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++) { for (unsigned i = 0; PeekToken(m.parser()) == TOK_FUNCTION; i++) {
// Get exclusive access to an empty LifoAlloc from the thread group's pool. // Get exclusive access to an empty LifoAlloc from the thread group's pool.
@ -5488,7 +5494,7 @@ CheckFunctionsParallelImpl(ModuleCompiler &m, ParallelGroupState &group)
return false; return false;
// Perform optimizations and LIR generation on a worker thread. // 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)) if (!StartOffThreadAsmJSCompile(m.cx(), task))
return false; return false;
@ -5507,9 +5513,14 @@ CheckFunctionsParallelImpl(ModuleCompiler &m, ParallelGroupState &group)
JS_ASSERT(group.outstandingJobs == 0); JS_ASSERT(group.outstandingJobs == 0);
JS_ASSERT(group.compiledJobs == m.numFunctions()); JS_ASSERT(group.compiledJobs == m.numFunctions());
JS_ASSERT(group.state.asmJSWorklist.empty()); #ifdef DEBUG
JS_ASSERT(group.state.asmJSFinishedList.empty()); {
JS_ASSERT(!group.state.asmJSWorkerFailed()); AutoLockWorkerThreadState lock;
JS_ASSERT(WorkerThreadState().asmJSWorklist().empty());
JS_ASSERT(WorkerThreadState().asmJSFinishedList().empty());
}
#endif
JS_ASSERT(!WorkerThreadState().asmJSWorkerFailed());
return true; return true;
} }
@ -5526,32 +5537,32 @@ CancelOutstandingJobs(ModuleCompiler &m, ParallelGroupState &group)
if (!group.outstandingJobs) if (!group.outstandingJobs)
return; return;
AutoLockWorkerThreadState lock(*m.cx()->workerThreadState()); AutoLockWorkerThreadState lock;
// From the compiling tasks, eliminate those waiting for worker assignation. // From the compiling tasks, eliminate those waiting for worker assignation.
group.outstandingJobs -= group.state.asmJSWorklist.length(); group.outstandingJobs -= WorkerThreadState().asmJSWorklist().length();
group.state.asmJSWorklist.clear(); WorkerThreadState().asmJSWorklist().clear();
// From the compiling tasks, eliminate those waiting for codegen. // From the compiling tasks, eliminate those waiting for codegen.
group.outstandingJobs -= group.state.asmJSFinishedList.length(); group.outstandingJobs -= WorkerThreadState().asmJSFinishedList().length();
group.state.asmJSFinishedList.clear(); WorkerThreadState().asmJSFinishedList().clear();
// Eliminate tasks that failed without adding to the finished list. // 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. // Any remaining tasks are therefore undergoing active compilation.
JS_ASSERT(group.outstandingJobs >= 0); JS_ASSERT(group.outstandingJobs >= 0);
while (group.outstandingJobs > 0) { while (group.outstandingJobs > 0) {
group.state.wait(WorkerThreadState::CONSUMER); WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
group.outstandingJobs -= group.state.harvestFailedAsmJSJobs(); group.outstandingJobs -= WorkerThreadState().harvestFailedAsmJSJobs();
group.outstandingJobs -= group.state.asmJSFinishedList.length(); group.outstandingJobs -= WorkerThreadState().asmJSFinishedList().length();
group.state.asmJSFinishedList.clear(); WorkerThreadState().asmJSFinishedList().clear();
} }
JS_ASSERT(group.outstandingJobs == 0); JS_ASSERT(group.outstandingJobs == 0);
JS_ASSERT(group.state.asmJSWorklist.empty()); JS_ASSERT(WorkerThreadState().asmJSWorklist().empty());
JS_ASSERT(group.state.asmJSFinishedList.empty()); JS_ASSERT(WorkerThreadState().asmJSFinishedList().empty());
} }
static const size_t LIFO_ALLOC_PARALLEL_CHUNK_SIZE = 1 << 12; 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 // constraint by hoisting asmJS* state out of WorkerThreadState so multiple
// concurrent asm.js parallel compilations don't race.) // concurrent asm.js parallel compilations don't race.)
ParallelCompilationGuard g; ParallelCompilationGuard g;
if (!ParallelCompilationEnabled(m.cx()) || !g.claim(m.cx()->workerThreadState())) if (!ParallelCompilationEnabled(m.cx()) || !g.claim())
return CheckFunctionsSequential(m); return CheckFunctionsSequential(m);
// Saturate all worker threads plus the main thread. // Saturate all worker threads plus the main thread.
WorkerThreadState &state = *m.cx()->workerThreadState(); size_t numParallelJobs = WorkerThreadState().threadCount + 1;
size_t numParallelJobs = state.numThreads + 1;
// Allocate scoped AsmJSParallelTask objects. Each contains a unique // Allocate scoped AsmJSParallelTask objects. Each contains a unique
// LifoAlloc that provides all necessary memory for compilation. // LifoAlloc that provides all necessary memory for compilation.
@ -5582,12 +5592,12 @@ CheckFunctionsParallel(ModuleCompiler &m)
tasks.infallibleAppend(LIFO_ALLOC_PARALLEL_CHUNK_SIZE); tasks.infallibleAppend(LIFO_ALLOC_PARALLEL_CHUNK_SIZE);
// With compilation memory in-scope, dispatch worker threads. // With compilation memory in-scope, dispatch worker threads.
ParallelGroupState group(state, tasks); ParallelGroupState group(tasks);
if (!CheckFunctionsParallelImpl(m, group)) { if (!CheckFunctionsParallelImpl(m, group)) {
CancelOutstandingJobs(m, group); CancelOutstandingJobs(m, group);
// If failure was triggered by a worker thread, report error. // 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<ModuleCompiler::Func *>(maybeFunc); ModuleCompiler::Func *func = reinterpret_cast<ModuleCompiler::Func *>(maybeFunc);
return m.failOffset(func->srcOffset(), "allocation failure during compilation"); return m.failOffset(func->srcOffset(), "allocation failure during compilation");
} }

View File

@ -513,15 +513,18 @@ jit::FinishOffThreadBuilder(IonBuilder *builder)
} }
static inline void 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++) { for (size_t i = 0; i < finished.length(); i++) {
IonBuilder *builder = compilations[i]; IonBuilder *builder = finished[i];
FinishOffThreadBuilder(builder); if (builder->compartment == CompileCompartment::get(comp)) {
FinishOffThreadBuilder(builder);
WorkerThreadState().remove(finished, &i);
}
} }
compilations.clear();
} }
/* static */ void /* static */ void
@ -543,7 +546,7 @@ JitCompartment::mark(JSTracer *trc, JSCompartment *compartment)
// do this for minor GCs. // do this for minor GCs.
JS_ASSERT(!trc->runtime->isHeapMinorCollecting()); JS_ASSERT(!trc->runtime->isHeapMinorCollecting());
CancelOffThreadIonCompile(compartment, nullptr); CancelOffThreadIonCompile(compartment, nullptr);
FinishAllOffThreadCompilations(this); FinishAllOffThreadCompilations(compartment);
// Free temporary OSR buffer. // Free temporary OSR buffer.
rt->freeOsrTempData(); rt->freeOsrTempData();
@ -1512,18 +1515,30 @@ AttachFinishedCompilations(JSContext *cx)
{ {
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
JitCompartment *ion = cx->compartment()->jitCompartment(); JitCompartment *ion = cx->compartment()->jitCompartment();
if (!ion || !cx->runtime()->workerThreadState) if (!ion)
return; return;
types::AutoEnterAnalysis enterTypes(cx); 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 // Incorporate any off thread compilations for the compartment which have
// have been cancelled. // finished, failed or have been cancelled.
while (!compilations.empty()) { while (true) {
IonBuilder *builder = compilations.popCopy(); 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()) { if (CodeGenerator *codegen = builder->backgroundCodegen()) {
RootedScript script(cx, builder->script()); RootedScript script(cx, builder->script());
@ -1538,7 +1553,7 @@ AttachFinishedCompilations(JSContext *cx)
{ {
// Release the worker thread lock and root the compiler for GC. // Release the worker thread lock and root the compiler for GC.
AutoTempAllocatorRooter root(cx, &builder->alloc()); AutoTempAllocatorRooter root(cx, &builder->alloc());
AutoUnlockWorkerThreadState unlock(cx->runtime()); AutoUnlockWorkerThreadState unlock;
AutoFlushCache afc("AttachFinishedCompilations", cx->runtime()->jitRuntime()); AutoFlushCache afc("AttachFinishedCompilations", cx->runtime()->jitRuntime());
success = codegen->link(cx, builder->constraints()); success = codegen->link(cx, builder->constraints());
} }
@ -1555,8 +1570,6 @@ AttachFinishedCompilations(JSContext *cx)
FinishOffThreadBuilder(builder); FinishOffThreadBuilder(builder);
} }
compilations.clear();
#endif #endif
} }
@ -1569,10 +1582,14 @@ OffThreadCompilationAvailable(JSContext *cx)
// on the main thread in some cases. Do not compile off thread during an // on the main thread in some cases. Do not compile off thread during an
// incremental GC, as this may trip incremental read barriers. // 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 // Skip off thread compilation if PC count profiling is enabled, as
// CodeGenerator::maybeCreateScriptCounts will not attach script profiles // CodeGenerator::maybeCreateScriptCounts will not attach script profiles
// when running off thread. // when running off thread.
return cx->runtime()->canUseParallelIonCompilation() return cx->runtime()->canUseParallelIonCompilation()
&& WorkerThreadState().cpuCount > 1
&& cx->runtime()->gcIncrementalState == gc::NO_INCREMENTAL && cx->runtime()->gcIncrementalState == gc::NO_INCREMENTAL
&& !cx->runtime()->profilingScripts; && !cx->runtime()->profilingScripts;
} }
@ -1832,7 +1849,9 @@ CheckScriptSize(JSContext *cx, JSScript* script)
if (script->length() > MAX_MAIN_THREAD_SCRIPT_SIZE || if (script->length() > MAX_MAIN_THREAD_SCRIPT_SIZE ||
numLocalsAndArgs > MAX_MAIN_THREAD_LOCALS_AND_ARGS) 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 // Even if off thread compilation is enabled, there are cases where
// compilation must still occur on the main thread. Don't compile // compilation must still occur on the main thread. Don't compile
// in these cases (except when profiling scripts, as compilations // in these cases (except when profiling scripts, as compilations
@ -2489,7 +2508,7 @@ jit::StopAllOffThreadCompilations(JSCompartment *comp)
if (!comp->jitCompartment()) if (!comp->jitCompartment())
return; return;
CancelOffThreadIonCompile(comp, nullptr); CancelOffThreadIonCompile(comp, nullptr);
FinishAllOffThreadCompilations(comp->jitCompartment()); FinishAllOffThreadCompilations(comp);
} }
void void

View File

@ -59,8 +59,6 @@ typedef void (*EnterJitCode)(void *code, unsigned argc, Value *argv, StackFrame
class IonBuilder; class IonBuilder;
typedef Vector<IonBuilder*, 0, SystemAllocPolicy> OffThreadCompilationVector;
// ICStubSpace is an abstraction for allocation policy and storage for stub data. // 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 // 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). // 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. // Ion state for the compartment's runtime.
JitRuntime *rt; 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. // Map ICStub keys to ICStub shared code objects.
typedef WeakValueCache<uint32_t, ReadBarriered<JitCode> > ICStubCodeMap; typedef WeakValueCache<uint32_t, ReadBarriered<JitCode> > ICStubCodeMap;
ICStubCodeMap *stubCodes_; ICStubCodeMap *stubCodes_;
@ -361,10 +353,6 @@ class JitCompartment
JitCode *generateStringConcatStub(JSContext *cx, ExecutionMode mode); JitCode *generateStringConcatStub(JSContext *cx, ExecutionMode mode);
public: public:
OffThreadCompilationVector &finishedOffThreadCompilations() {
return finishedOffThreadCompilations_;
}
JitCode *getStubCode(uint32_t key) { JitCode *getStubCode(uint32_t key) {
ICStubCodeMap::AddPtr p = stubCodes_->lookupForAdd(key); ICStubCodeMap::AddPtr p = stubCodes_->lookupForAdd(key);
if (p) if (p)

View File

@ -4529,7 +4529,7 @@ JS::FinishOffThreadScript(JSContext *maybecx, JSRuntime *rt, void *token)
if (maybecx) if (maybecx)
lfc.construct(maybecx); lfc.construct(maybecx);
return rt->workerThreadState->finishParseTask(maybecx, rt, token); return WorkerThreadState().finishParseTask(maybecx, rt, token);
#else #else
MOZ_ASSUME_UNREACHABLE("Off thread compilation is not available."); MOZ_ASSUME_UNREACHABLE("Off thread compilation is not available.");
#endif #endif

View File

@ -1052,9 +1052,6 @@ js::ThreadSafeContext::ThreadSafeContext(JSRuntime *rt, PerThreadData *pt, Conte
perThreadData(pt), perThreadData(pt),
allocator_(nullptr) allocator_(nullptr)
{ {
#ifdef JS_THREADSAFE
JS_ASSERT_IF(kind == Context_Exclusive, rt->workerThreadState != nullptr);
#endif
} }
bool bool

View File

@ -282,8 +282,6 @@ struct ThreadSafeContext : ContextFriendFields,
PropertyName *emptyString() { return runtime_->emptyString; } PropertyName *emptyString() { return runtime_->emptyString; }
FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); } FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); }
bool useHelperThreads() { return runtime_->useHelperThreads(); } bool useHelperThreads() { return runtime_->useHelperThreads(); }
unsigned cpuCount() { return runtime_->cpuCount(); }
size_t workerThreadCount() { return runtime_->workerThreadCount(); }
void *runtimeAddressForJit() { return runtime_; } void *runtimeAddressForJit() { return runtime_; }
void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; } void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; }
void *stackLimitAddressForJitCode(StackKind kind); void *stackLimitAddressForJitCode(StackKind kind);
@ -390,15 +388,6 @@ class ExclusiveContext : public ThreadSafeContext
return runtime_->scriptDataTable(); 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. // Methods specific to any WorkerThread for the context.
frontend::CompileError &addPendingCompileError(); frontend::CompileError &addPendingCompileError();
void addPendingOverRecursed(); void addPendingOverRecursed();
@ -1040,7 +1029,7 @@ class AutoLockForExclusiveAccess
void init(JSRuntime *rt) { void init(JSRuntime *rt) {
runtime = rt; runtime = rt;
if (runtime->numExclusiveThreads) { if (runtime->numExclusiveThreads) {
runtime->assertCanLock(JSRuntime::ExclusiveAccessLock); runtime->assertCanLock(ExclusiveAccessLock);
PR_Lock(runtime->exclusiveAccessLock); PR_Lock(runtime->exclusiveAccessLock);
#ifdef DEBUG #ifdef DEBUG
runtime->exclusiveAccessOwner = PR_GetCurrentThread(); runtime->exclusiveAccessOwner = PR_GetCurrentThread();
@ -1095,7 +1084,7 @@ class AutoLockForCompilation
void init(JSRuntime *rt) { void init(JSRuntime *rt) {
runtime = rt; runtime = rt;
if (runtime->numCompilationThreads) { if (runtime->numCompilationThreads) {
runtime->assertCanLock(JSRuntime::CompilationLock); runtime->assertCanLock(CompilationLock);
PR_Lock(runtime->compilationLock); PR_Lock(runtime->compilationLock);
#ifdef DEBUG #ifdef DEBUG
runtime->compilationLockOwner = PR_GetCurrentThread(); runtime->compilationLockOwner = PR_GetCurrentThread();

View File

@ -1713,9 +1713,9 @@ ArenaLists::refillFreeList(ThreadSafeContext *cx, AllocKind thingKind)
mozilla::Maybe<AutoLockWorkerThreadState> lock; mozilla::Maybe<AutoLockWorkerThreadState> lock;
JSRuntime *rt = zone->runtimeFromAnyThread(); JSRuntime *rt = zone->runtimeFromAnyThread();
if (rt->exclusiveThreadsPresent()) { if (rt->exclusiveThreadsPresent()) {
lock.construct<WorkerThreadState &>(*rt->workerThreadState); lock.construct();
while (rt->isHeapBusy()) while (rt->isHeapBusy())
rt->workerThreadState->wait(WorkerThreadState::PRODUCER); WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER);
} }
void *thing = cx->allocator()->arenas.allocateFromArenaInline(zone, thingKind); 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 // Lock the worker thread state when changing the heap state in the
// presence of exclusive threads, to avoid racing with refillFreeList. // presence of exclusive threads, to avoid racing with refillFreeList.
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
AutoLockWorkerThreadState lock(*rt->workerThreadState); AutoLockWorkerThreadState lock;
rt->heapState = heapState; rt->heapState = heapState;
#else #else
MOZ_CRASH(); MOZ_CRASH();
@ -4316,11 +4316,11 @@ AutoTraceSession::~AutoTraceSession()
if (runtime->exclusiveThreadsPresent()) { if (runtime->exclusiveThreadsPresent()) {
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
AutoLockWorkerThreadState lock(*runtime->workerThreadState); AutoLockWorkerThreadState lock;
runtime->heapState = prevState; runtime->heapState = prevState;
// Notify any worker threads waiting for the trace session to end. // Notify any worker threads waiting for the trace session to end.
runtime->workerThreadState->notifyAll(WorkerThreadState::PRODUCER); WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
#else #else
MOZ_CRASH(); MOZ_CRASH();
#endif #endif

View File

@ -1304,8 +1304,8 @@ ScriptSource::setSourceCopy(ExclusiveContext *cx, const jschar *src, uint32_t le
// progress on our compression task. // progress on our compression task.
const size_t HUGE_SCRIPT = 5 * 1024 * 1024; const size_t HUGE_SCRIPT = 5 * 1024 * 1024;
if (length < HUGE_SCRIPT && if (length < HUGE_SCRIPT &&
cx->cpuCount() > 1 && WorkerThreadState().cpuCount > 1 &&
cx->workerThreadCount() >= 2) WorkerThreadState().threadCount >= 2)
{ {
task->ss = this; task->ss = this;
task->chars = src; task->chars = src;

View File

@ -14,7 +14,6 @@
#include "prmjtime.h" #include "prmjtime.h"
#include "frontend/BytecodeCompiler.h" #include "frontend/BytecodeCompiler.h"
#include "jit/ExecutionModeInlines.h"
#include "jit/IonBuilder.h" #include "jit/IonBuilder.h"
#include "vm/Debugger.h" #include "vm/Debugger.h"
@ -28,31 +27,37 @@ using namespace js;
using mozilla::ArrayLength; using mozilla::ArrayLength;
using mozilla::DebugOnly; using mozilla::DebugOnly;
namespace js {
GlobalWorkerThreadState gWorkerThreadState;
} // namespace js
bool bool
js::EnsureWorkerThreadsInitialized(ExclusiveContext *cx) js::EnsureWorkerThreadsInitialized(ExclusiveContext *cx)
{ {
// If 'cx' is not a JSContext, we are already off the main thread and the // If 'cx' is not a JSContext, we are already off the main thread and the
// worker threads would have already been initialized. // worker threads would have already been initialized.
if (!cx->isJSContext()) { if (!cx->isJSContext())
JS_ASSERT(cx->workerThreadState() != nullptr);
return true;
}
JSRuntime *rt = cx->asJSContext()->runtime();
if (rt->workerThreadState)
return true; return true;
rt->workerThreadState = rt->new_<WorkerThreadState>(rt); return WorkerThreadState().ensureInitialized();
if (!rt->workerThreadState) }
return false;
if (!rt->workerThreadState->init()) { static size_t
js_delete(rt->workerThreadState); ThreadCountForCPUCount(size_t cpuCount)
rt->workerThreadState = nullptr; {
return false; 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 #ifdef JS_ION
@ -61,23 +66,19 @@ bool
js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData) js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData)
{ {
// Threads already initialized by the AsmJS compiler. // Threads already initialized by the AsmJS compiler.
JS_ASSERT(cx->workerThreadState() != nullptr);
JS_ASSERT(asmData->mir); JS_ASSERT(asmData->mir);
JS_ASSERT(asmData->lir == nullptr); JS_ASSERT(asmData->lir == nullptr);
WorkerThreadState &state = *cx->workerThreadState(); AutoLockWorkerThreadState lock;
JS_ASSERT(state.numThreads);
AutoLockWorkerThreadState lock(state);
// Don't append this task if another failed. // Don't append this task if another failed.
if (state.asmJSWorkerFailed()) if (WorkerThreadState().asmJSWorkerFailed())
return false; return false;
if (!state.asmJSWorklist.append(asmData)) if (!WorkerThreadState().asmJSWorklist().append(asmData))
return false; return false;
state.notifyOne(WorkerThreadState::PRODUCER); WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
return true; return true;
} }
@ -87,33 +88,26 @@ js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder)
if (!EnsureWorkerThreadsInitialized(cx)) if (!EnsureWorkerThreadsInitialized(cx))
return false; return false;
WorkerThreadState &state = *cx->runtime()->workerThreadState; AutoLockWorkerThreadState lock;
JS_ASSERT(state.numThreads);
AutoLockWorkerThreadState lock(state); if (!WorkerThreadState().ionWorklist().append(builder))
if (!state.ionWorklist.append(builder))
return false; return false;
cx->runtime()->addCompilationThread(); cx->runtime()->addCompilationThread();
state.notifyAll(WorkerThreadState::PRODUCER); WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
return true; return true;
} }
/* /*
* Move an IonBuilder for which compilation has either finished, failed, or * Move an IonBuilder for which compilation has either finished, failed, or
* been cancelled into the Ion compartment's finished compilations list. * been cancelled into the global finished compilation list. All off thread
* All off thread compilations which are started must eventually be finished. * compilations which are started must eventually be finished.
*/ */
static void static void
FinishOffThreadIonCompile(jit::IonBuilder *builder) FinishOffThreadIonCompile(jit::IonBuilder *builder)
{ {
JSCompartment *compartment = builder->script()->compartment(); WorkerThreadState().ionFinishedList().append(builder);
JS_ASSERT(compartment->runtimeFromAnyThread()->workerThreadState);
JS_ASSERT(compartment->runtimeFromAnyThread()->workerThreadState->isLocked());
compartment->jitCompartment()->finishedOffThreadCompilations().append(builder);
} }
#endif // JS_ION #endif // JS_ION
@ -130,49 +124,43 @@ void
js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script) js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
{ {
#ifdef JS_ION #ifdef JS_ION
JSRuntime *rt = compartment->runtimeFromMainThread();
if (!rt->workerThreadState)
return;
WorkerThreadState &state = *rt->workerThreadState;
jit::JitCompartment *jitComp = compartment->jitCompartment(); jit::JitCompartment *jitComp = compartment->jitCompartment();
if (!jitComp) if (!jitComp)
return; return;
AutoLockWorkerThreadState lock(state); AutoLockWorkerThreadState lock;
if (!WorkerThreadState().threads)
return;
/* Cancel any pending entries for which processing hasn't started. */ /* Cancel any pending entries for which processing hasn't started. */
for (size_t i = 0; i < state.ionWorklist.length(); i++) { GlobalWorkerThreadState::IonBuilderVector &worklist = WorkerThreadState().ionWorklist();
jit::IonBuilder *builder = state.ionWorklist[i]; for (size_t i = 0; i < worklist.length(); i++) {
jit::IonBuilder *builder = worklist[i];
if (CompiledScriptMatches(compartment, script, builder->script())) { if (CompiledScriptMatches(compartment, script, builder->script())) {
FinishOffThreadIonCompile(builder); FinishOffThreadIonCompile(builder);
state.ionWorklist[i--] = state.ionWorklist.back(); WorkerThreadState().remove(worklist, &i);
state.ionWorklist.popBack();
} }
} }
/* Wait for in progress entries to finish up. */ /* Wait for in progress entries to finish up. */
for (size_t i = 0; i < state.numThreads; i++) { for (size_t i = 0; i < WorkerThreadState().threadCount; i++) {
const WorkerThread &helper = state.threads[i]; const WorkerThread &helper = WorkerThreadState().threads[i];
while (helper.ionBuilder && while (helper.ionBuilder &&
CompiledScriptMatches(compartment, script, helper.ionBuilder->script())) CompiledScriptMatches(compartment, script, helper.ionBuilder->script()))
{ {
helper.ionBuilder->cancel(); helper.ionBuilder->cancel();
state.wait(WorkerThreadState::CONSUMER); WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
} }
} }
jit::OffThreadCompilationVector &compilations = jitComp->finishedOffThreadCompilations();
/* Cancel code generation for any completed entries. */ /* Cancel code generation for any completed entries. */
for (size_t i = 0; i < compilations.length(); i++) { GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList();
jit::IonBuilder *builder = compilations[i]; for (size_t i = 0; i < finished.length(); i++) {
jit::IonBuilder *builder = finished[i];
if (CompiledScriptMatches(compartment, script, builder->script())) { if (CompiledScriptMatches(compartment, script, builder->script())) {
jit::FinishOffThreadBuilder(builder); jit::FinishOffThreadBuilder(builder);
compilations[i--] = compilations.back(); WorkerThreadState().remove(finished, &i);
compilations.popBack();
} }
} }
#endif // JS_ION #endif // JS_ION
@ -237,6 +225,55 @@ ParseTask::~ParseTask()
js_delete(errors[i]); 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 bool
js::OffThreadParsingMustWaitForGC(JSRuntime *rt) js::OffThreadParsingMustWaitForGC(JSRuntime *rt)
{ {
@ -316,21 +353,19 @@ js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &optio
if (!task->init(cx, options)) if (!task->init(cx, options))
return false; return false;
WorkerThreadState &state = *cx->runtime()->workerThreadState;
JS_ASSERT(state.numThreads);
if (OffThreadParsingMustWaitForGC(cx->runtime())) { if (OffThreadParsingMustWaitForGC(cx->runtime())) {
if (!state.parseWaitingOnGC.append(task.get())) AutoLockWorkerThreadState lock;
if (!WorkerThreadState().parseWaitingOnGC().append(task.get()))
return false; return false;
} else { } else {
task->activate(cx->runtime()); task->activate(cx->runtime());
AutoLockWorkerThreadState lock(state); AutoLockWorkerThreadState lock;
if (!state.parseWorklist.append(task.get())) if (!WorkerThreadState().parseWorklist().append(task.get()))
return false; return false;
state.notifyAll(WorkerThreadState::PRODUCER); WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
} }
task.forget(); task.forget();
@ -343,45 +378,35 @@ js::EnqueuePendingParseTasksAfterGC(JSRuntime *rt)
{ {
JS_ASSERT(!OffThreadParsingMustWaitForGC(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; return;
// This logic should mirror the contents of the !activeGCInAtomsZone() // This logic should mirror the contents of the !activeGCInAtomsZone()
// branch in StartOffThreadParseScript: // 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++) AutoLockWorkerThreadState lock;
state.parseWaitingOnGC[i]->activate(rt);
AutoLockWorkerThreadState lock(state); for (size_t i = 0; i < newTasks.length(); i++)
WorkerThreadState().parseWorklist().append(newTasks[i]);
JS_ASSERT(state.parseWorklist.empty()); WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
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);
}
} }
#ifdef XP_WIN #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; static const uint32_t WORKER_STACK_QUOTA = 450 * 1024;
bool bool
WorkerThreadState::init() GlobalWorkerThreadState::ensureInitialized()
{ {
JS_ASSERT(numThreads == 0); JS_ASSERT(this == &WorkerThreadState());
AutoLockWorkerThreadState lock;
if (!runtime->useHelperThreads()) if (threads)
return true; return true;
workerLock = PR_NewLock(); threads = js_pod_calloc<WorkerThread>(threadCount);
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<WorkerThread>(runtime->workerThreadCount());
if (!threads) if (!threads)
return false; return false;
for (size_t i = 0; i < runtime->workerThreadCount(); i++) { for (size_t i = 0; i < threadCount; i++) {
WorkerThread &helper = threads[i]; WorkerThread &helper = threads[i];
helper.runtime = runtime; helper.threadData.construct(static_cast<JSRuntime *>(nullptr));
helper.threadData.construct(runtime);
helper.threadData.ref().addToThreadList();
helper.thread = PR_CreateThread(PR_USER_THREAD, helper.thread = PR_CreateThread(PR_USER_THREAD,
WorkerThread::ThreadMain, &helper, WorkerThread::ThreadMain, &helper,
PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, WORKER_STACK_SIZE); PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, WORKER_STACK_SIZE);
if (!helper.thread || !helper.threadData.ref().init()) { 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(); threads[j].destroy();
js_free(threads); js_free(threads);
threads = nullptr; threads = nullptr;
@ -436,50 +448,29 @@ WorkerThreadState::init()
} }
} }
numThreads = runtime->workerThreadCount();
resetAsmJSFailureState(); resetAsmJSFailureState();
return true; return true;
} }
void GlobalWorkerThreadState::GlobalWorkerThreadState()
WorkerThreadState::cleanup()
{ {
// Do preparatory work for shutdown before the final GC has destroyed most mozilla::PodZero(this);
// of the GC heap.
// Join created threads, to ensure there is no in progress work. cpuCount = GetCPUCount();
if (threads) { threadCount = ThreadCountForCPUCount(cpuCount);
for (size_t i = 0; i < numThreads; i++)
threads[i].destroy();
js_free(threads);
threads = nullptr;
numThreads = 0;
}
// Clean up any parse tasks which haven't been finished yet. MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
while (!parseFinishedList.empty())
finishParseTask(/* maybecx = */ nullptr, runtime, parseFinishedList[0]);
}
WorkerThreadState::~WorkerThreadState() workerLock = PR_NewLock();
{ consumerWakeup = PR_NewCondVar(workerLock);
JS_ASSERT(!threads); producerWakeup = PR_NewCondVar(workerLock);
JS_ASSERT(parseFinishedList.empty());
if (workerLock)
PR_DestroyLock(workerLock);
if (consumerWakeup)
PR_DestroyCondVar(consumerWakeup);
if (producerWakeup)
PR_DestroyCondVar(producerWakeup);
} }
void void
WorkerThreadState::lock() GlobalWorkerThreadState::lock()
{ {
runtime->assertCanLock(JSRuntime::WorkerThreadStateLock); JS_ASSERT(!isLocked());
AssertCurrentThreadCanLock(WorkerThreadStateLock);
PR_Lock(workerLock); PR_Lock(workerLock);
#ifdef DEBUG #ifdef DEBUG
lockOwner = PR_GetCurrentThread(); lockOwner = PR_GetCurrentThread();
@ -487,7 +478,7 @@ WorkerThreadState::lock()
} }
void void
WorkerThreadState::unlock() GlobalWorkerThreadState::unlock()
{ {
JS_ASSERT(isLocked()); JS_ASSERT(isLocked());
#ifdef DEBUG #ifdef DEBUG
@ -498,14 +489,14 @@ WorkerThreadState::unlock()
#ifdef DEBUG #ifdef DEBUG
bool bool
WorkerThreadState::isLocked() GlobalWorkerThreadState::isLocked()
{ {
return lockOwner == PR_GetCurrentThread(); return lockOwner == PR_GetCurrentThread();
} }
#endif #endif
void void
WorkerThreadState::wait(CondVar which, uint32_t millis) GlobalWorkerThreadState::wait(CondVar which, uint32_t millis)
{ {
JS_ASSERT(isLocked()); JS_ASSERT(isLocked());
#ifdef DEBUG #ifdef DEBUG
@ -521,37 +512,37 @@ WorkerThreadState::wait(CondVar which, uint32_t millis)
} }
void void
WorkerThreadState::notifyAll(CondVar which) GlobalWorkerThreadState::notifyAll(CondVar which)
{ {
JS_ASSERT(isLocked()); JS_ASSERT(isLocked());
PR_NotifyAllCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup); PR_NotifyAllCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
} }
void void
WorkerThreadState::notifyOne(CondVar which) GlobalWorkerThreadState::notifyOne(CondVar which)
{ {
JS_ASSERT(isLocked()); JS_ASSERT(isLocked());
PR_NotifyCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup); PR_NotifyCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
} }
bool bool
WorkerThreadState::canStartAsmJSCompile() GlobalWorkerThreadState::canStartAsmJSCompile()
{ {
// Don't execute an AsmJS job if an earlier one failed. // Don't execute an AsmJS job if an earlier one failed.
JS_ASSERT(isLocked()); JS_ASSERT(isLocked());
return (!asmJSWorklist.empty() && !numAsmJSFailedJobs); return (!asmJSWorklist().empty() && !numAsmJSFailedJobs);
} }
bool bool
WorkerThreadState::canStartIonCompile() GlobalWorkerThreadState::canStartIonCompile()
{ {
// A worker thread can begin an Ion compilation if (a) there is some script // 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 // which is waiting to be compiled, and (b) no other worker thread is
// currently compiling a script. The latter condition ensures that two // currently compiling a script. The latter condition ensures that two
// compilations cannot simultaneously occur. // compilations cannot simultaneously occur.
if (ionWorklist.empty()) if (ionWorklist().empty())
return false; return false;
for (size_t i = 0; i < numThreads; i++) { for (size_t i = 0; i < threadCount; i++) {
if (threads[i].ionBuilder) if (threads[i].ionBuilder)
return false; return false;
} }
@ -559,16 +550,16 @@ WorkerThreadState::canStartIonCompile()
} }
bool bool
WorkerThreadState::canStartParseTask() GlobalWorkerThreadState::canStartParseTask()
{ {
// Don't allow simultaneous off thread parses, to reduce contention on the // Don't allow simultaneous off thread parses, to reduce contention on the
// atoms table. Note that asm.js compilation depends on this to avoid // atoms table. Note that asm.js compilation depends on this to avoid
// stalling the worker thread, as off thread parse tasks can trigger and // stalling the worker thread, as off thread parse tasks can trigger and
// block on other off thread asm.js compilation tasks. // block on other off thread asm.js compilation tasks.
JS_ASSERT(isLocked()); JS_ASSERT(isLocked());
if (parseWorklist.empty()) if (parseWorklist().empty())
return false; return false;
for (size_t i = 0; i < numThreads; i++) { for (size_t i = 0; i < threadCount; i++) {
if (threads[i].parseTask) if (threads[i].parseTask)
return false; return false;
} }
@ -576,9 +567,9 @@ WorkerThreadState::canStartParseTask()
} }
bool bool
WorkerThreadState::canStartCompressionTask() GlobalWorkerThreadState::canStartCompressionTask()
{ {
return !compressionWorklist.empty(); return !compressionWorklist().empty();
} }
static void static void
@ -609,19 +600,19 @@ CallNewScriptHookForAllScripts(JSContext *cx, HandleScript script)
} }
JSScript * JSScript *
WorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token) GlobalWorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token)
{ {
ParseTask *parseTask = nullptr; ParseTask *parseTask = nullptr;
// The token is a ParseTask* which should be in the finished list. // The token is a ParseTask* which should be in the finished list.
// Find and remove its entry. // Find and remove its entry.
{ {
AutoLockWorkerThreadState lock(*rt->workerThreadState); AutoLockWorkerThreadState lock;
for (size_t i = 0; i < parseFinishedList.length(); i++) { ParseTaskVector &finished = parseFinishedList();
if (parseFinishedList[i] == token) { for (size_t i = 0; i < finished.length(); i++) {
parseTask = parseFinishedList[i]; if (finished[i] == token) {
parseFinishedList[i] = parseFinishedList.back(); parseTask = finished[i];
parseFinishedList.popBack(); remove(finished, &i);
break; break;
} }
} }
@ -691,24 +682,20 @@ WorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *toke
void void
WorkerThread::destroy() WorkerThread::destroy()
{ {
WorkerThreadState &state = *runtime->workerThreadState;
if (thread) { if (thread) {
{ {
AutoLockWorkerThreadState lock(state); AutoLockWorkerThreadState lock;
terminate = true; terminate = true;
/* Notify all workers, to ensure that this thread wakes up. */ /* Notify all workers, to ensure that this thread wakes up. */
state.notifyAll(WorkerThreadState::PRODUCER); WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
} }
PR_JoinThread(thread); PR_JoinThread(thread);
} }
if (!threadData.empty()) { if (!threadData.empty())
threadData.ref().removeFromThreadList();
threadData.destroy(); threadData.destroy();
}
} }
/* static */ /* static */
@ -720,19 +707,23 @@ WorkerThread::ThreadMain(void *arg)
} }
void void
WorkerThread::handleAsmJSWorkload(WorkerThreadState &state) WorkerThread::handleAsmJSWorkload()
{ {
#ifdef JS_ION #ifdef JS_ION
JS_ASSERT(state.isLocked()); JS_ASSERT(WorkerThreadState().isLocked());
JS_ASSERT(state.canStartAsmJSCompile()); JS_ASSERT(WorkerThreadState().canStartAsmJSCompile());
JS_ASSERT(idle()); JS_ASSERT(idle());
asmData = state.asmJSWorklist.popCopy(); asmData = WorkerThreadState().asmJSWorklist().popCopy();
bool success = false; bool success = false;
state.unlock();
do { 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(); int64_t before = PRMJ_Now();
@ -748,38 +739,35 @@ WorkerThread::handleAsmJSWorkload(WorkerThreadState &state)
success = true; success = true;
} while(0); } while(0);
state.lock();
// On failure, signal parent for harvesting in CancelOutstandingJobs(). // On failure, signal parent for harvesting in CancelOutstandingJobs().
if (!success) { if (!success) {
state.noteAsmJSFailure(asmData->func); WorkerThreadState().noteAsmJSFailure(asmData->func);
state.notifyAll(WorkerThreadState::CONSUMER); WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
asmData = nullptr; asmData = nullptr;
return; return;
} }
// On success, move work to the finished list. // On success, move work to the finished list.
state.asmJSFinishedList.append(asmData); WorkerThreadState().asmJSFinishedList().append(asmData);
asmData = nullptr; asmData = nullptr;
// Notify the main thread in case it's blocked waiting for a LifoAlloc. // Notify the main thread in case it's blocked waiting for a LifoAlloc.
state.notifyAll(WorkerThreadState::CONSUMER); WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
#else #else
MOZ_CRASH(); MOZ_CRASH();
#endif // JS_ION #endif // JS_ION
} }
void void
WorkerThread::handleIonWorkload(WorkerThreadState &state) WorkerThread::handleIonWorkload()
{ {
#ifdef JS_ION #ifdef JS_ION
JS_ASSERT(state.isLocked()); JS_ASSERT(WorkerThreadState().isLocked());
JS_ASSERT(state.canStartIonCompile()); JS_ASSERT(WorkerThreadState().canStartIonCompile());
JS_ASSERT(idle()); JS_ASSERT(idle());
ionBuilder = state.ionWorklist.popCopy(); ionBuilder = WorkerThreadState().ionWorklist().popCopy();
DebugOnly<ExecutionMode> executionMode = ionBuilder->info().executionMode();
#if JS_TRACE_LOGGING #if JS_TRACE_LOGGING
AutoTraceLog logger(TraceLogging::getLogger(TraceLogging::ION_BACKGROUND_COMPILER), AutoTraceLog logger(TraceLogging::getLogger(TraceLogging::ION_BACKGROUND_COMPILER),
@ -788,9 +776,13 @@ WorkerThread::handleIonWorkload(WorkerThreadState &state)
ionBuilder->script()); ionBuilder->script());
#endif #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()), jit::CompileCompartment::get(ionBuilder->script()->compartment()),
&ionBuilder->alloc()); &ionBuilder->alloc());
AutoEnterIonCompilation ionCompiling; AutoEnterIonCompilation ionCompiling;
@ -799,7 +791,6 @@ WorkerThread::handleIonWorkload(WorkerThreadState &state)
if (succeeded) if (succeeded)
ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder)); ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder));
} }
state.lock();
FinishOffThreadIonCompile(ionBuilder); FinishOffThreadIonCompile(ionBuilder);
ionBuilder = nullptr; ionBuilder = nullptr;
@ -808,10 +799,10 @@ WorkerThread::handleIonWorkload(WorkerThreadState &state)
// at the next operation callback. Don't interrupt Ion code for this, as // at the next operation callback. Don't interrupt Ion code for this, as
// this incorporation can be delayed indefinitely without affecting // this incorporation can be delayed indefinitely without affecting
// performance as long as the main thread is actually executing Ion code. // 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. // Notify the main thread in case it is waiting for the compilation to finish.
state.notifyAll(WorkerThreadState::CONSUMER); WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
#else #else
MOZ_CRASH(); MOZ_CRASH();
#endif // JS_ION #endif // JS_ION
@ -843,17 +834,19 @@ ExclusiveContext::addPendingOverRecursed()
} }
void void
WorkerThread::handleParseWorkload(WorkerThreadState &state) WorkerThread::handleParseWorkload()
{ {
JS_ASSERT(state.isLocked()); JS_ASSERT(WorkerThreadState().isLocked());
JS_ASSERT(state.canStartParseTask()); JS_ASSERT(WorkerThreadState().canStartParseTask());
JS_ASSERT(idle()); JS_ASSERT(idle());
parseTask = state.parseWorklist.popCopy(); parseTask = WorkerThreadState().parseWorklist().popCopy();
parseTask->cx->setWorkerThread(this); 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, parseTask->script = frontend::CompileScript(parseTask->cx, &parseTask->alloc,
NullPtr(), NullPtr(), NullPtr(), NullPtr(),
parseTask->options, parseTask->options,
@ -865,26 +858,26 @@ WorkerThread::handleParseWorkload(WorkerThreadState &state)
// FinishOffThreadScript will need to be called on the script to // FinishOffThreadScript will need to be called on the script to
// migrate it into the correct compartment. // migrate it into the correct compartment.
state.parseFinishedList.append(parseTask); WorkerThreadState().parseFinishedList().append(parseTask);
parseTask = nullptr; parseTask = nullptr;
// Notify the main thread in case it is waiting for the parse/emit to finish. // Notify the main thread in case it is waiting for the parse/emit to finish.
state.notifyAll(WorkerThreadState::CONSUMER); WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
} }
void void
WorkerThread::handleCompressionWorkload(WorkerThreadState &state) WorkerThread::handleCompressionWorkload()
{ {
JS_ASSERT(state.isLocked()); JS_ASSERT(WorkerThreadState().isLocked());
JS_ASSERT(state.canStartCompressionTask()); JS_ASSERT(WorkerThreadState().canStartCompressionTask());
JS_ASSERT(idle()); JS_ASSERT(idle());
compressionTask = state.compressionWorklist.popCopy(); compressionTask = WorkerThreadState().compressionWorklist().popCopy();
compressionTask->workerThread = this; compressionTask->workerThread = this;
{ {
AutoUnlockWorkerThreadState unlock(runtime); AutoUnlockWorkerThreadState unlock;
if (!compressionTask->work()) if (!compressionTask->work())
compressionTask->setOOM(); compressionTask->setOOM();
} }
@ -893,7 +886,7 @@ WorkerThread::handleCompressionWorkload(WorkerThreadState &state)
compressionTask = nullptr; compressionTask = nullptr;
// Notify the main thread in case it is waiting for the compression to finish. // Notify the main thread in case it is waiting for the compression to finish.
state.notifyAll(WorkerThreadState::CONSUMER); WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
} }
bool bool
@ -902,25 +895,24 @@ js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
if (!EnsureWorkerThreadsInitialized(cx)) if (!EnsureWorkerThreadsInitialized(cx))
return false; return false;
WorkerThreadState &state = *cx->workerThreadState(); AutoLockWorkerThreadState lock;
AutoLockWorkerThreadState lock(state);
if (!state.compressionWorklist.append(task)) if (!WorkerThreadState().compressionWorklist().append(task))
return false; return false;
state.notifyAll(WorkerThreadState::PRODUCER); WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
return true; return true;
} }
bool bool
WorkerThreadState::compressionInProgress(SourceCompressionTask *task) GlobalWorkerThreadState::compressionInProgress(SourceCompressionTask *task)
{ {
JS_ASSERT(isLocked()); JS_ASSERT(isLocked());
for (size_t i = 0; i < compressionWorklist.length(); i++) { for (size_t i = 0; i < compressionWorklist().length(); i++) {
if (compressionWorklist[i] == task) if (compressionWorklist()[i] == task)
return true; return true;
} }
for (size_t i = 0; i < numThreads; i++) { for (size_t i = 0; i < threadCount; i++) {
if (threads[i].compressionTask == task) if (threads[i].compressionTask == task)
return true; return true;
} }
@ -932,11 +924,10 @@ SourceCompressionTask::complete()
{ {
JS_ASSERT_IF(!ss, !chars); JS_ASSERT_IF(!ss, !chars);
if (active()) { if (active()) {
WorkerThreadState &state = *cx->workerThreadState(); AutoLockWorkerThreadState lock;
AutoLockWorkerThreadState lock(state);
while (state.compressionInProgress(this)) while (WorkerThreadState().compressionInProgress(this))
state.wait(WorkerThreadState::CONSUMER); WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
ss->ready_ = true; ss->ready_ = true;
@ -955,15 +946,15 @@ SourceCompressionTask::complete()
} }
SourceCompressionTask * SourceCompressionTask *
WorkerThreadState::compressionTaskForSource(ScriptSource *ss) GlobalWorkerThreadState::compressionTaskForSource(ScriptSource *ss)
{ {
JS_ASSERT(isLocked()); JS_ASSERT(isLocked());
for (size_t i = 0; i < compressionWorklist.length(); i++) { for (size_t i = 0; i < compressionWorklist().length(); i++) {
SourceCompressionTask *task = compressionWorklist[i]; SourceCompressionTask *task = compressionWorklist()[i];
if (task->source() == ss) if (task->source() == ss)
return task; return task;
} }
for (size_t i = 0; i < numThreads; i++) { for (size_t i = 0; i < threadCount; i++) {
SourceCompressionTask *task = threads[i].compressionTask; SourceCompressionTask *task = threads[i].compressionTask;
if (task && task->source() == ss) if (task && task->source() == ss)
return task; return task;
@ -981,12 +972,11 @@ ScriptSource::getOffThreadCompressionChars(ExclusiveContext *cx)
return nullptr; return nullptr;
} }
WorkerThreadState &state = *cx->workerThreadState(); AutoLockWorkerThreadState lock;
AutoLockWorkerThreadState lock(state);
// Look for a token that hasn't finished compressing and whose source is // Look for a token that hasn't finished compressing and whose source is
// the given ScriptSource. // the given ScriptSource.
if (SourceCompressionTask *task = state.compressionTaskForSource(this)) if (SourceCompressionTask *task = WorkerThreadState().compressionTaskForSource(this))
return task->uncompressedChars(); return task->uncompressedChars();
// Compressing has finished, so this ScriptSource is ready. Avoid future // Compressing has finished, so this ScriptSource is ready. Avoid future
@ -1000,8 +990,7 @@ void
WorkerThread::threadLoop() WorkerThread::threadLoop()
{ {
JS::AutoAssertNoGC nogc; JS::AutoAssertNoGC nogc;
WorkerThreadState &state = *runtime->workerThreadState; AutoLockWorkerThreadState lock;
AutoLockWorkerThreadState lock(state);
js::TlsPerThreadData.set(threadData.addr()); js::TlsPerThreadData.set(threadData.addr());
@ -1022,25 +1011,25 @@ WorkerThread::threadLoop()
while (true) { while (true) {
if (terminate) if (terminate)
return; return;
if (state.canStartIonCompile() || if (WorkerThreadState().canStartIonCompile() ||
state.canStartAsmJSCompile() || WorkerThreadState().canStartAsmJSCompile() ||
state.canStartParseTask() || WorkerThreadState().canStartParseTask() ||
state.canStartCompressionTask()) WorkerThreadState().canStartCompressionTask())
{ {
break; break;
} }
state.wait(WorkerThreadState::PRODUCER); WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER);
} }
// Dispatch tasks, prioritizing AsmJS work. // Dispatch tasks, prioritizing AsmJS work.
if (state.canStartAsmJSCompile()) if (WorkerThreadState().canStartAsmJSCompile())
handleAsmJSWorkload(state); handleAsmJSWorkload();
else if (state.canStartIonCompile()) else if (WorkerThreadState().canStartIonCompile())
handleIonWorkload(state); handleIonWorkload();
else if (state.canStartParseTask()) else if (WorkerThreadState().canStartParseTask())
handleParseWorkload(state); handleParseWorkload();
else if (state.canStartCompressionTask()) else if (WorkerThreadState().canStartCompressionTask())
handleCompressionWorkload(state); handleCompressionWorkload();
else else
MOZ_ASSUME_UNREACHABLE("No task to perform"); MOZ_ASSUME_UNREACHABLE("No task to perform");
} }
@ -1071,6 +1060,11 @@ js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
{ {
} }
void
js::CancelOffThreadParses(JSRuntime *rt)
{
}
bool bool
js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options, js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options,
const jschar *chars, size_t length, HandleObject scopeChain, 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"); MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
} }
void
js::WaitForOffThreadParsingToFinish(JSRuntime *rt)
{
}
bool bool
js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task) js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
{ {

View File

@ -33,59 +33,58 @@ namespace jit {
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
/* Per-runtime state for off thread work items. */ // Per-process state for off thread work items.
class WorkerThreadState class GlobalWorkerThreadState
{ {
public: 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<jit::IonBuilder*, 0, SystemAllocPolicy> IonBuilderVector;
typedef Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> AsmJSParallelTaskVector;
typedef Vector<ParseTask*, 0, SystemAllocPolicy> ParseTaskVector;
typedef Vector<SourceCompressionTask*, 0, SystemAllocPolicy> SourceCompressionTaskVector;
// List of available threads, or null if the thread state has not been initialized.
WorkerThread *threads; WorkerThread *threads;
size_t numThreads;
enum CondVar { private:
/* For notifying threads waiting for work that they may be able to make progress. */ // The lists below are all protected by |lock|.
CONSUMER,
/* For notifying threads doing work that they may be able to make progress. */ // Ion compilation worklist and finished jobs.
PRODUCER IonBuilderVector ionWorklist_, ionFinishedList_;
};
/* Shared worklist for Ion worker threads. */ // AsmJS worklist and finished jobs.
Vector<jit::IonBuilder*, 0, SystemAllocPolicy> ionWorklist; //
// 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. */ public:
Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> asmJSWorklist; // For now, only allow a single parallel asm.js compilation to happen at a
// time. This avoids race conditions on asmJSWorklist/asmJSFinishedList/etc.
/*
* 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<AsmJSParallelTask*, 0, SystemAllocPolicy> asmJSFinishedList;
/*
* 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<uint32_t> asmJSCompilationInProgress; mozilla::Atomic<uint32_t> asmJSCompilationInProgress;
/* Shared worklist for parsing/emitting scripts on worker threads. */ private:
Vector<ParseTask*, 0, SystemAllocPolicy> parseWorklist, parseFinishedList; // 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. */ // Parse tasks waiting for an atoms-zone GC to complete.
Vector<ParseTask*, 0, SystemAllocPolicy> parseWaitingOnGC; ParseTaskVector parseWaitingOnGC_;
/* Worklist for source compression worker threads. */ // Source compression worklist.
Vector<SourceCompressionTask *, 0, SystemAllocPolicy> compressionWorklist; SourceCompressionTaskVector compressionWorklist_;
WorkerThreadState(JSRuntime *rt) { public:
mozilla::PodZero(this); GlobalWorkerThreadState();
runtime = rt;
}
~WorkerThreadState();
bool init();
void cleanup();
bool ensureInitialized();
void lock(); void lock();
void unlock(); void unlock();
@ -93,10 +92,62 @@ class WorkerThreadState
bool isLocked(); bool isLocked();
# endif # 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 wait(CondVar which, uint32_t timeoutMillis = 0);
void notifyAll(CondVar which); void notifyAll(CondVar which);
void notifyOne(CondVar which); void notifyOne(CondVar which);
// Helper method for removing items from the vectors below while iterating over them.
template <typename T>
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 canStartAsmJSCompile();
bool canStartIonCompile(); bool canStartIonCompile();
bool canStartParseTask(); bool canStartParseTask();
@ -132,8 +183,6 @@ class WorkerThreadState
private: private:
JSRuntime *runtime;
/* /*
* Lock protecting all mutable shared state accessed by helper threads, and * Lock protecting all mutable shared state accessed by helper threads, and
* used by all condition variables. * used by all condition variables.
@ -161,11 +210,16 @@ class WorkerThreadState
void *asmJSFailedFunction; void *asmJSFailedFunction;
}; };
static inline GlobalWorkerThreadState &
WorkerThreadState()
{
extern GlobalWorkerThreadState gWorkerThreadState;
return gWorkerThreadState;
}
/* Individual helper thread, one allocated per core. */ /* Individual helper thread, one allocated per core. */
struct WorkerThread struct WorkerThread
{ {
JSRuntime *runtime;
mozilla::Maybe<PerThreadData> threadData; mozilla::Maybe<PerThreadData> threadData;
PRThread *thread; PRThread *thread;
@ -190,10 +244,10 @@ struct WorkerThread
void destroy(); void destroy();
void handleAsmJSWorkload(WorkerThreadState &state); void handleAsmJSWorkload();
void handleIonWorkload(WorkerThreadState &state); void handleIonWorkload();
void handleParseWorkload(WorkerThreadState &state); void handleParseWorkload();
void handleCompressionWorkload(WorkerThreadState &state); void handleCompressionWorkload();
static void ThreadMain(void *arg); static void ThreadMain(void *arg);
void threadLoop(); void threadLoop();
@ -203,10 +257,15 @@ struct WorkerThread
/* Methods for interacting with worker threads. */ /* Methods for interacting with worker threads. */
/* Initialize worker threads unless already initialized. */ // Initialize worker threads unless already initialized.
bool bool
EnsureWorkerThreadsInitialized(ExclusiveContext *cx); 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 #ifdef JS_ION
/* Perform MIR optimization and LIR generation on a single function. */ /* Perform MIR optimization and LIR generation on a single function. */
@ -229,6 +288,10 @@ StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder);
void void
CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script); 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 * Start a parse/emit cycle for a stream of source. The characters must stay
* alive until the compilation finishes. * alive until the compilation finishes.
@ -245,10 +308,6 @@ StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options,
void void
EnqueuePendingParseTasksAfterGC(JSRuntime *rt); 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. */ /* Start a compression job for the specified token. */
bool bool
StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task); StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task);
@ -258,24 +317,19 @@ class AutoLockWorkerThreadState
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
WorkerThreadState &state;
public: public:
AutoLockWorkerThreadState(WorkerThreadState &state AutoLockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: state(state)
{ {
MOZ_GUARD_OBJECT_NOTIFIER_INIT; MOZ_GUARD_OBJECT_NOTIFIER_INIT;
state.lock(); WorkerThreadState().lock();
} }
~AutoLockWorkerThreadState() { ~AutoLockWorkerThreadState() {
state.unlock(); WorkerThreadState().unlock();
} }
#else #else
public: public:
AutoLockWorkerThreadState(WorkerThreadState &state AutoLockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
{ {
MOZ_GUARD_OBJECT_NOTIFIER_INIT; MOZ_GUARD_OBJECT_NOTIFIER_INIT;
} }
@ -284,28 +338,22 @@ class AutoLockWorkerThreadState
class AutoUnlockWorkerThreadState class AutoUnlockWorkerThreadState
{ {
JSRuntime *rt;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
public: public:
AutoUnlockWorkerThreadState(JSRuntime *rt AutoUnlockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: rt(rt)
{ {
MOZ_GUARD_OBJECT_NOTIFIER_INIT; MOZ_GUARD_OBJECT_NOTIFIER_INIT;
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
JS_ASSERT(rt->workerThreadState); WorkerThreadState().unlock();
rt->workerThreadState->unlock();
#else
(void)this->rt;
#endif #endif
} }
~AutoUnlockWorkerThreadState() ~AutoUnlockWorkerThreadState()
{ {
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
rt->workerThreadState->lock(); WorkerThreadState().lock();
#endif #endif
} }
}; };
@ -313,6 +361,7 @@ class AutoUnlockWorkerThreadState
#ifdef JS_ION #ifdef JS_ION
struct AsmJSParallelTask struct AsmJSParallelTask
{ {
JSRuntime *runtime; // Associated runtime.
LifoAlloc lifo; // Provider of all heap memory used for compilation. LifoAlloc lifo; // Provider of all heap memory used for compilation.
void *func; // Really, a ModuleCompiler::Func* void *func; // Really, a ModuleCompiler::Func*
jit::MIRGenerator *mir; // Passed from main thread to worker. jit::MIRGenerator *mir; // Passed from main thread to worker.
@ -320,10 +369,11 @@ struct AsmJSParallelTask
unsigned compileTime; unsigned compileTime;
AsmJSParallelTask(size_t defaultChunkSize) 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->func = func;
this->mir = mir; this->mir = mir;
this->lir = nullptr; this->lir = nullptr;
@ -376,6 +426,10 @@ struct ParseTask
void activate(JSRuntime *rt); void activate(JSRuntime *rt);
void finish(); void finish();
bool runtimeMatches(JSRuntime *rt) {
return exclusiveContextGlobal->runtimeFromAnyThread() == rt;
}
~ParseTask(); ~ParseTask();
}; };

View File

@ -5456,7 +5456,7 @@ ProcessArgs(JSContext *cx, JSObject *obj_, OptionParser *op)
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
int32_t threadCount = op->getIntOption("thread-count"); int32_t threadCount = op->getIntOption("thread-count");
if (threadCount >= 0) if (threadCount >= 0)
cx->runtime()->setFakeCPUCount(threadCount); SetFakeCPUCount(threadCount);
#endif /* JS_THREADSAFE */ #endif /* JS_THREADSAFE */
#if defined(JS_ION) #if defined(JS_ION)

View File

@ -91,9 +91,6 @@ PerThreadData::~PerThreadData()
if (dtoaState) if (dtoaState)
js_DestroyDtoaState(dtoaState); js_DestroyDtoaState(dtoaState);
if (isInList())
removeFromThreadList();
#ifdef JS_ARM_SIMULATOR #ifdef JS_ARM_SIMULATOR
js_delete(simulator_); js_delete(simulator_);
#endif #endif
@ -109,22 +106,6 @@ PerThreadData::init()
return true; 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 = { static const JSWrapObjectCallbacks DefaultWrapObjectCallbacks = {
TransparentObjectWrapper, TransparentObjectWrapper,
nullptr, nullptr,
@ -144,7 +125,6 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
operationCallbackLock(nullptr), operationCallbackLock(nullptr),
operationCallbackOwner(nullptr), operationCallbackOwner(nullptr),
workerThreadState(nullptr),
exclusiveAccessLock(nullptr), exclusiveAccessLock(nullptr),
exclusiveAccessOwner(nullptr), exclusiveAccessOwner(nullptr),
mainThreadHasExclusiveAccess(false), mainThreadHasExclusiveAccess(false),
@ -316,11 +296,6 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
parallelWarmup(0), parallelWarmup(0),
ionReturnOverride_(MagicValue(JS_ARG_POISON)), ionReturnOverride_(MagicValue(JS_ARG_POISON)),
useHelperThreads_(useHelperThreads), useHelperThreads_(useHelperThreads),
#ifdef JS_THREADSAFE
cpuCount_(GetCPUCount()),
#else
cpuCount_(1),
#endif
parallelIonCompilationEnabled_(true), parallelIonCompilationEnabled_(true),
parallelParsingEnabled_(true), parallelParsingEnabled_(true),
isWorkerRuntime_(false) isWorkerRuntime_(false)
@ -328,8 +303,6 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
, enteredPolicy(nullptr) , enteredPolicy(nullptr)
#endif #endif
{ {
MOZ_ASSERT(cpuCount_ > 0, "GetCPUCount() seems broken");
liveRuntimesCount++; liveRuntimesCount++;
setGCMode(JSGC_MODE_GLOBAL); setGCMode(JSGC_MODE_GLOBAL);
@ -392,7 +365,6 @@ JSRuntime::init(uint32_t maxbytes)
return false; return false;
js::TlsPerThreadData.set(&mainThread); js::TlsPerThreadData.set(&mainThread);
mainThread.addToThreadList();
if (!threadPool.init()) if (!threadPool.init())
return false; return false;
@ -463,15 +435,15 @@ JSRuntime::~JSRuntime()
/* Free source hook early, as its destructor may want to delete roots. */ /* Free source hook early, as its destructor may want to delete roots. */
sourceHook = nullptr; 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()) for (CompartmentsIter comp(this, SkipAtoms); !comp.done(); comp.next())
CancelOffThreadIonCompile(comp, nullptr); CancelOffThreadIonCompile(comp, nullptr);
WaitForOffThreadParsingToFinish(this); CancelOffThreadParses(this);
#ifdef JS_THREADSAFE
if (workerThreadState)
workerThreadState->cleanup();
#endif
/* Poison common names before final GC. */ /* Poison common names before final GC. */
FinishCommonNames(this); FinishCommonNames(this);
@ -504,11 +476,7 @@ JSRuntime::~JSRuntime()
*/ */
finishSelfHosting(); finishSelfHosting();
mainThread.removeFromThreadList();
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
js_delete(workerThreadState);
JS_ASSERT(!exclusiveAccessOwner); JS_ASSERT(!exclusiveAccessOwner);
if (exclusiveAccessLock) if (exclusiveAccessLock)
PR_DestroyLock(exclusiveAccessLock); PR_DestroyLock(exclusiveAccessLock);
@ -1010,7 +978,7 @@ JSRuntime::assertCanLock(RuntimeLock which)
case ExclusiveAccessLock: case ExclusiveAccessLock:
JS_ASSERT(exclusiveAccessOwner != PR_GetCurrentThread()); JS_ASSERT(exclusiveAccessOwner != PR_GetCurrentThread());
case WorkerThreadStateLock: case WorkerThreadStateLock:
JS_ASSERT_IF(workerThreadState, !workerThreadState->isLocked()); JS_ASSERT(!WorkerThreadState().isLocked());
case CompilationLock: case CompilationLock:
JS_ASSERT(compilationLockOwner != PR_GetCurrentThread()); JS_ASSERT(compilationLockOwner != PR_GetCurrentThread());
case OperationCallbackLock: case OperationCallbackLock:
@ -1082,4 +1050,14 @@ js::CurrentThreadCanReadCompilationData()
#endif #endif
} }
void
js::AssertCurrentThreadCanLock(RuntimeLock which)
{
#ifdef JS_THREADSAFE
PerThreadData *pt = TlsPerThreadData.get();
if (pt && pt->runtime_)
pt->runtime_->assertCanLock(which);
#endif
}
#endif // DEBUG #endif // DEBUG

View File

@ -84,7 +84,6 @@ class Activation;
class ActivationIterator; class ActivationIterator;
class AsmJSActivation; class AsmJSActivation;
class MathCache; class MathCache;
class WorkerThreadState;
namespace jit { namespace jit {
class JitRuntime; class JitRuntime;
@ -477,15 +476,31 @@ AtomStateOffsetToName(const JSAtomState &atomState, size_t offset)
return *(js::FixedHeapPtr<js::PropertyName>*)((char*)&atomState + offset); return *(js::FixedHeapPtr<js::PropertyName>*)((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 * Encapsulates portions of the runtime/context that are tied to a
* single active thread. Normally, as most JS is single-threaded, * single active thread. Instances of this structure can occur for
* there is only one instance of this struct, embedded in the * the main thread as |JSRuntime::mainThread|, for select operations
* JSRuntime as the field |mainThread|. During Parallel JS sections, * performed off thread, such as parsing, and for Parallel JS worker
* however, there will be one instance per worker thread. * threads.
*/ */
class PerThreadData : public PerThreadDataFriendFields, class PerThreadData : public PerThreadDataFriendFields
public mozilla::LinkedListElement<PerThreadData>
{ {
/* /*
* Backpointer to the full shared JSRuntime* with which this * Backpointer to the full shared JSRuntime* with which this
@ -538,6 +553,7 @@ class PerThreadData : public PerThreadDataFriendFields,
friend class js::AsmJSActivation; friend class js::AsmJSActivation;
#ifdef DEBUG #ifdef DEBUG
friend bool js::CurrentThreadCanReadCompilationData(); friend bool js::CurrentThreadCanReadCompilationData();
friend void js::AssertCurrentThreadCanLock(RuntimeLock which);
#endif #endif
/* /*
@ -598,8 +614,6 @@ class PerThreadData : public PerThreadDataFriendFields,
~PerThreadData(); ~PerThreadData();
bool init(); bool init();
void addToThreadList();
void removeFromThreadList();
bool associatedWith(const JSRuntime *rt) { return runtime_ == rt; } bool associatedWith(const JSRuntime *rt) { return runtime_ == rt; }
inline JSRuntime *runtimeFromMainThread(); inline JSRuntime *runtimeFromMainThread();
@ -609,6 +623,25 @@ class PerThreadData : public PerThreadDataFriendFields,
inline void addActiveCompilation(); inline void addActiveCompilation();
inline void removeActiveCompilation(); 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 #ifdef JS_ARM_SIMULATOR
js::jit::Simulator *simulator() const; js::jit::Simulator *simulator() const;
void setSimulator(js::jit::Simulator *sim); void setSimulator(js::jit::Simulator *sim);
@ -646,12 +679,6 @@ struct JSRuntime : public JS::shadow::Runtime,
*/ */
js::PerThreadData mainThread; 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<js::PerThreadData> threadList;
/* /*
* If non-zero, we were been asked to call the operation callback as soon * If non-zero, we were been asked to call the operation callback as soon
* as possible. * as possible.
@ -668,20 +695,10 @@ struct JSRuntime : public JS::shadow::Runtime,
/* Branch callback */ /* Branch callback */
JSOperationCallback operationCallback; 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 #ifdef DEBUG
void assertCanLock(RuntimeLock which); void assertCanLock(js::RuntimeLock which);
#else #else
void assertCanLock(RuntimeLock which) {} void assertCanLock(js::RuntimeLock which) {}
#endif #endif
private: private:
@ -702,7 +719,7 @@ struct JSRuntime : public JS::shadow::Runtime,
public: public:
AutoLockForOperationCallback(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : rt(rt) { AutoLockForOperationCallback(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : rt(rt) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT; MOZ_GUARD_OBJECT_NOTIFIER_INIT;
rt->assertCanLock(JSRuntime::OperationCallbackLock); rt->assertCanLock(js::OperationCallbackLock);
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
PR_Lock(rt->operationCallbackLock); PR_Lock(rt->operationCallbackLock);
rt->operationCallbackOwner = PR_GetCurrentThread(); rt->operationCallbackOwner = PR_GetCurrentThread();
@ -733,8 +750,6 @@ struct JSRuntime : public JS::shadow::Runtime,
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
js::WorkerThreadState *workerThreadState;
private: private:
/* /*
* Lock taken when using per-runtime or per-zone data that could otherwise * 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() { void lockGC() {
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
assertCanLock(GCLock); assertCanLock(js::GCLock);
PR_Lock(gcLock); PR_Lock(gcLock);
JS_ASSERT(!gcLockOwner); JS_ASSERT(!gcLockOwner);
#ifdef DEBUG #ifdef DEBUG
@ -1717,7 +1732,6 @@ struct JSRuntime : public JS::shadow::Runtime,
private: private:
JSUseHelperThreads useHelperThreads_; JSUseHelperThreads useHelperThreads_;
unsigned cpuCount_;
// Settings for how helper threads can be used. // Settings for how helper threads can be used.
bool parallelIonCompilationEnabled_; bool parallelIonCompilationEnabled_;
@ -1739,39 +1753,14 @@ struct JSRuntime : public JS::shadow::Runtime,
#endif #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 // Note: these values may be toggled dynamically (in response to about:config
// prefs changing). // prefs changing).
void setParallelIonCompilationEnabled(bool value) { void setParallelIonCompilationEnabled(bool value) {
parallelIonCompilationEnabled_ = value; parallelIonCompilationEnabled_ = value;
} }
bool canUseParallelIonCompilation() const { bool canUseParallelIonCompilation() const {
// Require cpuCount_ > 1 so that Ion compilation jobs and main-thread
// execution are not competing for the same resources.
return useHelperThreads() && return useHelperThreads() &&
parallelIonCompilationEnabled_ && parallelIonCompilationEnabled_;
cpuCount_ > 1;
} }
void setParallelParsingEnabled(bool value) { void setParallelParsingEnabled(bool value) {
parallelParsingEnabled_ = value; parallelParsingEnabled_ = value;

View File

@ -392,7 +392,7 @@ uint32_t
ThreadPool::numWorkers() const ThreadPool::numWorkers() const
{ {
// Subtract one for the main thread, which always exists. // Subtract one for the main thread, which always exists.
return runtime_->cpuCount() - 1; return WorkerThreadState().cpuCount - 1;
} }
bool bool