mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
457 lines
12 KiB
C++
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 */
|