| /* -*- 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" |
| |
| #include "prmjtime.h" |
| |
| #ifdef JS_PARALLEL_COMPILATION |
| # include "jit/AsmJS.h" |
| # include "jit/IonBuilder.h" |
| # include "jit/ExecutionModeInlines.h" |
| #endif |
| |
| #if defined(JS_THREADSAFE) && defined(STARBOARD) |
| #include "pr_starboard.h" |
| #endif // defined(JS_THREADSAFE) && defined(STARBOARD) |
| |
| 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, jit::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(jit::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; |
| |
| jit::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++) { |
| jit::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); |
| } |
| } |
| |
| jit::OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations(); |
| |
| /* Cancel code generation for any completed entries. */ |
| for (size_t i = 0; i < compilations.length(); i++) { |
| jit::IonBuilder *builder = compilations[i]; |
| if (CompiledScriptMatches(compartment, script, builder->script())) { |
| jit::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 { |
| jit::IonContext icx(asmData->mir->compartment, &asmData->mir->temp()); |
| |
| int64_t before = PRMJ_Now(); |
| |
| if (!OptimizeMIR(asmData->mir)) |
| break; |
| |
| asmData->lir = GenerateLIR(asmData->mir); |
| if (!asmData->lir) |
| break; |
| |
| int64_t after = PRMJ_Now(); |
| asmData->compileTime = (after - before) / PRMJ_USEC_PER_MSEC; |
| |
| 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<jit::ExecutionMode> executionMode = ionBuilder->info().executionMode(); |
| JS_ASSERT(GetIonScript(ionBuilder->script(), executionMode) == ION_COMPILING_SCRIPT); |
| |
| state.unlock(); |
| { |
| jit::IonContext ictx(ionBuilder->script()->compartment(), &ionBuilder->temp()); |
| ionBuilder->setBackgroundCodegen(jit::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, jit::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 */ |