/* -*- 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 using namespace js; using mozilla::DebugOnly; #ifdef JS_PARALLEL_COMPILATION bool js::EnsureParallelCompilationInitialized(JSRuntime *rt) { if (rt->workerThreadState) return true; rt->workerThreadState = rt->new_(); 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 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(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 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 */