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-02-05 11:40:35 -07:00
parent f419ff98ef
commit 874c41cf84
15 changed files with 573 additions and 521 deletions

View File

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

View File

@ -5418,20 +5418,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;
}
};
@ -5445,22 +5445,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<AsmJSParallelTask> &tasks;
int32_t outstandingJobs; // Good work, jobs!
uint32_t compiledJobs;
ParallelGroupState(WorkerThreadState &state, js::Vector<AsmJSParallelTask> &tasks)
: state(state), tasks(tasks), outstandingJobs(0), compiledJobs(0)
ParallelGroupState(js::Vector<AsmJSParallelTask> &tasks)
: tasks(tasks), outstandingJobs(0), compiledJobs(0)
{ }
};
@ -5468,14 +5469,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;
@ -5525,9 +5526,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.
@ -5542,7 +5548,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;
@ -5561,9 +5567,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;
}
@ -5580,32 +5591,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;
@ -5619,12 +5630,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.
@ -5636,12 +5646,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<ModuleCompiler::Func *>(maybeFunc);
return m.failOffset(func->srcOffset(), "allocation failure during compilation");
}

View File

@ -512,15 +512,20 @@ jit::FinishOffThreadBuilder(IonBuilder *builder)
}
static inline void
FinishAllOffThreadCompilations(JitCompartment *ion)
FinishAllOffThreadCompilations(JSCompartment *comp)
{
OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations();
#ifdef JS_THREADSAFE
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();
#endif
}
/* static */ void
@ -542,7 +547,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();
@ -1511,18 +1516,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());
@ -1537,7 +1554,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());
}
@ -1554,8 +1571,6 @@ AttachFinishedCompilations(JSContext *cx)
FinishOffThreadBuilder(builder);
}
compilations.clear();
#endif
}
@ -1564,16 +1579,24 @@ static const size_t BUILDER_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12;
static inline bool
OffThreadCompilationAvailable(JSContext *cx)
{
#ifdef JS_THREADSAFE
// Even if off thread compilation is enabled, compilation must still occur
// 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;
#else
return false;
#endif
}
static void
@ -1831,7 +1854,12 @@ CheckScriptSize(JSContext *cx, JSScript* script)
if (script->length() > MAX_MAIN_THREAD_SCRIPT_SIZE ||
numLocalsAndArgs > MAX_MAIN_THREAD_LOCALS_AND_ARGS)
{
if (cx->runtime()->canUseParallelIonCompilation()) {
#ifdef JS_THREADSAFE
size_t cpuCount = WorkerThreadState().cpuCount;
#else
size_t cpuCount = 1;
#endif
if (cx->runtime()->canUseParallelIonCompilation() && 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
@ -2488,7 +2516,7 @@ jit::StopAllOffThreadCompilations(JSCompartment *comp)
if (!comp->jitCompartment())
return;
CancelOffThreadIonCompile(comp, nullptr);
FinishAllOffThreadCompilations(comp->jitCompartment());
FinishAllOffThreadCompilations(comp);
}
void

View File

@ -59,8 +59,6 @@ typedef void (*EnterJitCode)(void *code, unsigned argc, Value *argv, StackFrame
class IonBuilder;
typedef Vector<IonBuilder*, 0, SystemAllocPolicy> 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<uint32_t, ReadBarriered<JitCode> > 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)

View File

@ -615,6 +615,8 @@ JS_ShutDown(void)
}
#endif
WorkerThreadState().finish();
PRMJ_NowShutdown();
#if EXPOSE_INTL_API
@ -4552,7 +4554,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

View File

@ -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

View File

@ -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();

View File

@ -1713,9 +1713,9 @@ ArenaLists::refillFreeList(ThreadSafeContext *cx, AllocKind thingKind)
mozilla::Maybe<AutoLockWorkerThreadState> lock;
JSRuntime *rt = zone->runtimeFromAnyThread();
if (rt->exclusiveThreadsPresent()) {
lock.construct<WorkerThreadState &>(*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

View File

@ -1313,11 +1313,15 @@ ScriptSource::setSourceCopy(ExclusiveContext *cx, const jschar *src, uint32_t le
// thread (see WorkerThreadState::canStartParseTask) which would cause a
// deadlock if there wasn't a second worker thread that could make
// progress on our compression task.
#ifdef JS_THREADSAFE
bool canCompressOffThread =
WorkerThreadState().cpuCount > 1 &&
WorkerThreadState().threadCount >= 2;
#else
bool canCompressOffThread = false;
#endif
const size_t HUGE_SCRIPT = 5 * 1024 * 1024;
if (length < HUGE_SCRIPT &&
cx->cpuCount() > 1 &&
cx->workerThreadCount() >= 2)
{
if (length < HUGE_SCRIPT && canCompressOffThread) {
task->ss = this;
task->chars = src;
ready_ = false;

View File

@ -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_<WorkerThreadState>(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<WorkerThread>(runtime->workerThreadCount());
threads = js_pod_calloc<WorkerThread>(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<JSRuntime *>(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,43 @@ 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.
cpuCount = GetCPUCount();
threadCount = ThreadCountForCPUCount(cpuCount);
MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
workerLock = PR_NewLock();
consumerWakeup = PR_NewCondVar(workerLock);
producerWakeup = PR_NewCondVar(workerLock);
}
void
GlobalWorkerThreadState::finish()
{
if (threads) {
for (size_t i = 0; i < numThreads; i++)
for (size_t i = 0; i < threadCount; i++)
threads[i].destroy();
js_free(threads);
threads = nullptr;
numThreads = 0;
}
// Clean up any parse tasks which haven't been finished yet.
while (!parseFinishedList.empty())
finishParseTask(/* maybecx = */ nullptr, runtime, parseFinishedList[0]);
}
WorkerThreadState::~WorkerThreadState()
{
JS_ASSERT(!threads);
JS_ASSERT(parseFinishedList.empty());
if (workerLock)
PR_DestroyLock(workerLock);
if (consumerWakeup)
PR_DestroyCondVar(consumerWakeup);
if (producerWakeup)
PR_DestroyCondVar(producerWakeup);
PR_DestroyCondVar(consumerWakeup);
PR_DestroyCondVar(producerWakeup);
PR_DestroyLock(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 +492,7 @@ WorkerThreadState::lock()
}
void
WorkerThreadState::unlock()
GlobalWorkerThreadState::unlock()
{
JS_ASSERT(isLocked());
#ifdef DEBUG
@ -498,14 +503,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 +526,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 +564,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 +581,9 @@ WorkerThreadState::canStartParseTask()
}
bool
WorkerThreadState::canStartCompressionTask()
GlobalWorkerThreadState::canStartCompressionTask()
{
return !compressionWorklist.empty();
return !compressionWorklist().empty();
}
static void
@ -609,19 +614,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 +696,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 +721,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 +753,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> executionMode = ionBuilder->info().executionMode();
ionBuilder = WorkerThreadState().ionWorklist().popCopy();
#if JS_TRACE_LOGGING
AutoTraceLog logger(TraceLogging::getLogger(TraceLogging::ION_BACKGROUND_COMPILER),
@ -788,9 +790,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 +805,6 @@ WorkerThread::handleIonWorkload(WorkerThreadState &state)
if (succeeded)
ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder));
}
state.lock();
FinishOffThreadIonCompile(ionBuilder);
ionBuilder = nullptr;
@ -808,10 +813,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 +848,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 +872,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 +900,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 +909,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 +938,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 +960,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 +986,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 +1004,7 @@ void
WorkerThread::threadLoop()
{
JS::AutoAssertNoGC nogc;
WorkerThreadState &state = *runtime->workerThreadState;
AutoLockWorkerThreadState lock(state);
AutoLockWorkerThreadState lock;
js::TlsPerThreadData.set(threadData.addr());
@ -1022,25 +1025,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 +1074,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 +1087,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)
{

View File

@ -33,58 +33,59 @@ 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<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;
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<jit::IonBuilder*, 0, SystemAllocPolicy> 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<AsmJSParallelTask*, 0, SystemAllocPolicy> 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<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.
*/
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<uint32_t> asmJSCompilationInProgress;
/* Shared worklist for parsing/emitting scripts on worker threads. */
Vector<ParseTask*, 0, SystemAllocPolicy> 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<ParseTask*, 0, SystemAllocPolicy> parseWaitingOnGC;
// Parse tasks waiting for an atoms-zone GC to complete.
ParseTaskVector parseWaitingOnGC_;
/* Worklist for source compression worker threads. */
Vector<SourceCompressionTask *, 0, SystemAllocPolicy> compressionWorklist;
// Source compression worklist.
SourceCompressionTaskVector compressionWorklist_;
WorkerThreadState(JSRuntime *rt) {
mozilla::PodZero(this);
runtime = rt;
}
~WorkerThreadState();
public:
GlobalWorkerThreadState();
bool init();
void cleanup();
bool ensureInitialized();
void finish();
void lock();
void unlock();
@ -93,10 +94,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 <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 canStartIonCompile();
bool canStartParseTask();
@ -132,8 +185,6 @@ class WorkerThreadState
private:
JSRuntime *runtime;
/*
* Lock protecting all mutable shared state accessed by helper threads, and
* used by all condition variables.
@ -161,11 +212,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<PerThreadData> threadData;
PRThread *thread;
@ -190,10 +246,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 +259,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 +290,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 +310,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 +319,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 +340,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 +363,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 +371,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 +428,10 @@ struct ParseTask
void activate(JSRuntime *rt);
void finish();
bool runtimeMatches(JSRuntime *rt) {
return exclusiveContextGlobal->runtimeFromAnyThread() == rt;
}
~ParseTask();
};

View File

@ -5470,7 +5470,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)

View File

@ -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

View File

@ -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<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
* 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<PerThreadData>
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<js::PerThreadData> 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;

View File

@ -391,8 +391,12 @@ ThreadPool::init()
uint32_t
ThreadPool::numWorkers() const
{
#ifdef JS_THREADSAFE
// Subtract one for the main thread, which always exists.
return runtime_->cpuCount() - 1;
return WorkerThreadState().cpuCount - 1;
#else
return 0;
#endif
}
bool