gecko/js/src/jsworkers.cpp

457 lines
12 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jsworkers.h"
#include "mozilla/DebugOnly.h"
#ifdef JS_PARALLEL_COMPILATION
# include "ion/AsmJS.h"
# include "ion/IonBuilder.h"
# include "ion/ExecutionModeInlines.h"
#endif
using namespace js;
using mozilla::DebugOnly;
#ifdef JS_PARALLEL_COMPILATION
bool
js::EnsureParallelCompilationInitialized(JSRuntime *rt)
{
if (rt->workerThreadState)
return true;
rt->workerThreadState = rt->new_<WorkerThreadState>();
if (!rt->workerThreadState)
return false;
if (!rt->workerThreadState->init(rt)) {
js_delete(rt->workerThreadState);
rt->workerThreadState = NULL;
return false;
}
return true;
}
bool
js::StartOffThreadAsmJSCompile(JSContext *cx, AsmJSParallelTask *asmData)
{
// Threads already initialized by the AsmJS compiler.
JS_ASSERT(cx->runtime->workerThreadState);
JS_ASSERT(asmData->mir);
JS_ASSERT(asmData->lir == NULL);
WorkerThreadState &state = *cx->runtime->workerThreadState;
JS_ASSERT(state.numThreads);
AutoLockWorkerThreadState lock(cx->runtime);
// Don't append this task if another failed.
if (state.asmJSWorkerFailed())
return false;
if (!state.asmJSWorklist.append(asmData))
return false;
state.notify(WorkerThreadState::WORKER);
return true;
}
bool
js::StartOffThreadIonCompile(JSContext *cx, ion::IonBuilder *builder)
{
JSRuntime *rt = cx->runtime;
if (!EnsureParallelCompilationInitialized(rt))
return false;
WorkerThreadState &state = *cx->runtime->workerThreadState;
JS_ASSERT(state.numThreads);
AutoLockWorkerThreadState lock(rt);
if (!state.ionWorklist.append(builder))
return false;
state.notify(WorkerThreadState::WORKER);
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.
*/
static void
FinishOffThreadIonCompile(ion::IonBuilder *builder)
{
JSCompartment *compartment = builder->script()->compartment();
JS_ASSERT(compartment->rt->workerThreadState);
JS_ASSERT(compartment->rt->workerThreadState->isLocked());
compartment->ionCompartment()->finishedOffThreadCompilations().append(builder);
}
static inline bool
CompiledScriptMatches(JSCompartment *compartment, JSScript *script, JSScript *target)
{
if (script)
return target == script;
return target->compartment() == compartment;
}
void
js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
{
if (!compartment->rt->workerThreadState)
return;
WorkerThreadState &state = *compartment->rt->workerThreadState;
ion::IonCompartment *ion = compartment->ionCompartment();
if (!ion)
return;
AutoLockWorkerThreadState lock(compartment->rt);
/* Cancel any pending entries for which processing hasn't started. */
for (size_t i = 0; i < state.ionWorklist.length(); i++) {
ion::IonBuilder *builder = state.ionWorklist[i];
if (CompiledScriptMatches(compartment, script, builder->script())) {
FinishOffThreadIonCompile(builder);
state.ionWorklist[i--] = state.ionWorklist.back();
state.ionWorklist.popBack();
}
}
/* Wait for in progress entries to finish up. */
for (size_t i = 0; i < state.numThreads; i++) {
const WorkerThread &helper = state.threads[i];
while (helper.ionBuilder &&
CompiledScriptMatches(compartment, script, helper.ionBuilder->script()))
{
helper.ionBuilder->cancel();
state.wait(WorkerThreadState::MAIN);
}
}
ion::OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations();
/* Cancel code generation for any completed entries. */
for (size_t i = 0; i < compilations.length(); i++) {
ion::IonBuilder *builder = compilations[i];
if (CompiledScriptMatches(compartment, script, builder->script())) {
ion::FinishOffThreadBuilder(builder);
compilations[i--] = compilations.back();
compilations.popBack();
}
}
}
bool
WorkerThreadState::init(JSRuntime *rt)
{
if (!rt->useHelperThreads()) {
numThreads = 0;
return true;
}
workerLock = PR_NewLock();
if (!workerLock)
return false;
mainWakeup = PR_NewCondVar(workerLock);
if (!mainWakeup)
return false;
helperWakeup = PR_NewCondVar(workerLock);
if (!helperWakeup)
return false;
numThreads = rt->helperThreadCount();
threads = (WorkerThread*) rt->calloc_(sizeof(WorkerThread) * numThreads);
if (!threads) {
numThreads = 0;
return false;
}
for (size_t i = 0; i < numThreads; i++) {
WorkerThread &helper = threads[i];
helper.runtime = rt;
helper.thread = PR_CreateThread(PR_USER_THREAD,
WorkerThread::ThreadMain, &helper,
PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
if (!helper.thread) {
for (size_t j = 0; j < numThreads; j++)
threads[j].destroy();
js_delete(threads);
threads = NULL;
numThreads = 0;
return false;
}
}
resetAsmJSFailureState();
return true;
}
WorkerThreadState::~WorkerThreadState()
{
/*
* Join created threads first, which needs locks and condition variables
* to be intact.
*/
if (threads) {
for (size_t i = 0; i < numThreads; i++)
threads[i].destroy();
js_delete(threads);
}
if (workerLock)
PR_DestroyLock(workerLock);
if (mainWakeup)
PR_DestroyCondVar(mainWakeup);
if (helperWakeup)
PR_DestroyCondVar(helperWakeup);
}
void
WorkerThreadState::lock()
{
JS_ASSERT(!isLocked());
PR_Lock(workerLock);
#ifdef DEBUG
lockOwner = PR_GetCurrentThread();
#endif
}
void
WorkerThreadState::unlock()
{
JS_ASSERT(isLocked());
#ifdef DEBUG
lockOwner = NULL;
#endif
PR_Unlock(workerLock);
}
#ifdef DEBUG
bool
WorkerThreadState::isLocked()
{
return lockOwner == PR_GetCurrentThread();
}
#endif
void
WorkerThreadState::wait(CondVar which, uint32_t millis)
{
JS_ASSERT(isLocked());
#ifdef DEBUG
lockOwner = NULL;
#endif
DebugOnly<PRStatus> status =
PR_WaitCondVar((which == MAIN) ? mainWakeup : helperWakeup,
millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
JS_ASSERT(status == PR_SUCCESS);
#ifdef DEBUG
lockOwner = PR_GetCurrentThread();
#endif
}
void
WorkerThreadState::notify(CondVar which)
{
JS_ASSERT(isLocked());
PR_NotifyCondVar((which == MAIN) ? mainWakeup : helperWakeup);
}
void
WorkerThreadState::notifyAll(CondVar which)
{
JS_ASSERT(isLocked());
PR_NotifyAllCondVar((which == MAIN) ? mainWakeup : helperWakeup);
}
bool
WorkerThreadState::canStartAsmJSCompile()
{
// Don't execute an AsmJS job if an earlier one failed.
JS_ASSERT(isLocked());
return (!asmJSWorklist.empty() && !numAsmJSFailedJobs);
}
bool
WorkerThreadState::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())
return false;
for (size_t i = 0; i < numThreads; i++) {
if (threads[i].ionBuilder)
return false;
}
return true;
}
void
WorkerThread::destroy()
{
WorkerThreadState &state = *runtime->workerThreadState;
if (!thread)
return;
{
AutoLockWorkerThreadState lock(runtime);
terminate = true;
/* Notify all workers, to ensure that this thread wakes up. */
state.notifyAll(WorkerThreadState::WORKER);
}
PR_JoinThread(thread);
}
/* static */
void
WorkerThread::ThreadMain(void *arg)
{
PR_SetCurrentThreadName("Analysis Helper");
static_cast<WorkerThread *>(arg)->threadLoop();
}
void
WorkerThread::handleAsmJSWorkload(WorkerThreadState &state)
{
JS_ASSERT(state.isLocked());
JS_ASSERT(state.canStartAsmJSCompile());
JS_ASSERT(!ionBuilder && !asmData);
asmData = state.asmJSWorklist.popCopy();
bool success = false;
state.unlock();
do {
ion::IonContext icx(asmData->mir->compartment, &asmData->mir->temp());
if (!OptimizeMIR(asmData->mir))
break;
asmData->lir = GenerateLIR(asmData->mir);
if (!asmData->lir)
break;
success = true;
} while(0);
state.lock();
// On failure, signal parent for harvesting in CancelOutstandingJobs().
if (!success) {
asmData = NULL;
state.noteAsmJSFailure(asmData->funcNum);
state.notify(WorkerThreadState::MAIN);
return;
}
// On success, move work to the finished list.
state.asmJSFinishedList.append(asmData);
asmData = NULL;
// Notify the main thread in case it's blocked waiting for a LifoAlloc.
state.notify(WorkerThreadState::MAIN);
}
void
WorkerThread::handleIonWorkload(WorkerThreadState &state)
{
JS_ASSERT(state.isLocked());
JS_ASSERT(state.canStartIonCompile());
JS_ASSERT(!ionBuilder && !asmData);
ionBuilder = state.ionWorklist.popCopy();
DebugOnly<ion::ExecutionMode> executionMode = ionBuilder->info().executionMode();
JS_ASSERT(GetIonScript(ionBuilder->script(), executionMode) == ION_COMPILING_SCRIPT);
state.unlock();
{
ion::IonContext ictx(ionBuilder->script()->compartment(), &ionBuilder->temp());
ionBuilder->setBackgroundCodegen(ion::CompileBackEnd(ionBuilder));
}
state.lock();
FinishOffThreadIonCompile(ionBuilder);
ionBuilder = NULL;
// Notify the main thread in case it is waiting for the compilation to finish.
state.notify(WorkerThreadState::MAIN);
// Ping the main thread so that the compiled code can be incorporated
// at the next operation callback.
runtime->triggerOperationCallback();
}
void
WorkerThread::threadLoop()
{
WorkerThreadState &state = *runtime->workerThreadState;
state.lock();
threadData.construct(runtime);
js::TlsPerThreadData.set(threadData.addr());
while (true) {
JS_ASSERT(!ionBuilder && !asmData);
// Block until an Ion or AsmJS task is available.
while (!state.canStartIonCompile() && !state.canStartAsmJSCompile()) {
if (terminate) {
state.unlock();
return;
}
state.wait(WorkerThreadState::WORKER);
}
// Dispatch tasks, prioritizing AsmJS work.
if (state.canStartAsmJSCompile())
handleAsmJSWorkload(state);
else if (state.canStartIonCompile())
handleIonWorkload(state);
}
}
#else /* JS_PARALLEL_COMPILATION */
bool
js::StartOffThreadAsmJSCompile(JSContext *cx, AsmJSParallelTask *asmData)
{
JS_NOT_REACHED("Off thread compilation not available in non-THREADSAFE builds");
return false;
}
bool
js::StartOffThreadIonCompile(JSContext *cx, ion::IonBuilder *builder)
{
JS_NOT_REACHED("Off thread compilation not available in non-THREADSAFE builds");
return false;
}
void
js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
{
}
#endif /* JS_PARALLEL_COMPILATION */