diff --git a/js/public/Vector.h b/js/public/Vector.h index e9a5cce1db3..01cabc9895a 100644 --- a/js/public/Vector.h +++ b/js/public/Vector.h @@ -1,5 +1,5 @@ /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sw=4 et tw=99 ft=cpp: + * vim: set ts=4 sw=4 et tw=99 ft=cpp: * * 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 @@ -297,7 +297,7 @@ class Vector : private AllocPolicy #endif /* Append operations guaranteed to succeed due to pre-reserved space. */ - template void internalAppend(U t); + template void internalAppend(U u); void internalAppendN(const T &t, size_t n); template void internalAppend(const U *begin, size_t length); template void internalAppend(const Vector &other); @@ -395,8 +395,11 @@ class Vector : private AllocPolicy /* mutators */ + /* Given that the Vector is empty and has no inline storage, grow to |capacity|. */ + bool initCapacity(size_t capacity); + /* If reserve(length() + N) succeeds, the N next appends are guaranteed to succeed. */ - bool reserve(size_t capacity); + bool reserve(size_t request); /* * Destroy elements in the range [end() - incr, end()). Does not deallocate @@ -441,8 +444,8 @@ class Vector : private AllocPolicy * Guaranteed-infallible append operations for use upon vectors whose * memory has been pre-reserved. */ - void infallibleAppend(const T &t) { - internalAppend(t); + template void infallibleAppend(const U &u) { + internalAppend(u); } void infallibleAppendN(const T &t, size_t n) { internalAppendN(t, n); @@ -699,6 +702,25 @@ Vector::growStorageBy(size_t incr) return Impl::growTo(*this, newCap); } +template +inline bool +Vector::initCapacity(size_t capacity) +{ + JS_ASSERT(empty()); + JS_ASSERT(usingInlineStorage()); + if (capacity == 0) + return true; + T *newbuf = reinterpret_cast(this->malloc_(capacity * sizeof(T))); + if (!newbuf) + return false; + mBegin = newbuf; + mCapacity = capacity; +#ifdef DEBUG + mReserved = capacity; +#endif + return true; +} + template inline bool Vector::reserve(size_t request) @@ -837,11 +859,11 @@ Vector::append(U t) template template JS_ALWAYS_INLINE void -Vector::internalAppend(U t) +Vector::internalAppend(U u) { JS_ASSERT(mLength + 1 <= mReserved); JS_ASSERT(mReserved <= mCapacity); - new(endNoCheck()) T(t); + new(endNoCheck()) T(u); ++mLength; } diff --git a/js/src/ion/AsmJS.cpp b/js/src/ion/AsmJS.cpp index ba5c0fe7b12..bcac41c7e10 100644 --- a/js/src/ion/AsmJS.cpp +++ b/js/src/ion/AsmJS.cpp @@ -6,6 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "jsmath.h" +#include "jsworkers.h" + #include "frontend/ParseNode.h" #include "ion/AsmJS.h" #include "ion/AsmJSModule.h" @@ -1029,7 +1031,7 @@ class ModuleCompiler typedef Vector GlobalAccessVector; JSContext * cx_; - IonContext ionContext_; + IonContext ictx_; MacroAssembler masm_; ScopedJSDeletePtr module_; @@ -1066,8 +1068,8 @@ class ModuleCompiler public: ModuleCompiler(JSContext *cx, TokenStream &ts) : cx_(cx), - ionContext_(cx->runtime), // TODO: This serves as a temp assertion. - masm_(), + ictx_(cx->runtime), + masm_(cx), moduleFunctionName_(NULL), globalArgumentName_(NULL), importArgumentName_(NULL), @@ -4431,6 +4433,194 @@ CheckFunctionBodiesSequential(ModuleCompiler &m) return true; } +#ifdef JS_PARALLEL_COMPILATION +// State of compilation as tracked and updated by the main thread. +struct ParallelGroupState +{ + WorkerThreadState &state; + Vector &tasks; + int32_t outstandingJobs; // Good work, jobs! + uint32_t compiledJobs; + + ParallelGroupState(WorkerThreadState &state, Vector &tasks) + : state(state), tasks(tasks), outstandingJobs(0), compiledJobs(0) + { } +}; + +// Block until a worker-assigned LifoAlloc becomes finished. +static AsmJSParallelTask * +GetFinishedCompilation(ModuleCompiler &m, ParallelGroupState &group) +{ + AutoLockWorkerThreadState lock(m.cx()->runtime); + + while (!group.state.asmJSWorkerFailed()) { + if (!group.state.asmJSFinishedList.empty()) { + group.outstandingJobs--; + return group.state.asmJSFinishedList.popCopy(); + } + group.state.wait(WorkerThreadState::MAIN); + } + + return NULL; +} + +static bool +GenerateCodeForFinishedJob(ModuleCompiler &m, ParallelGroupState &group, AsmJSParallelTask **outTask) +{ + // Block until a used LifoAlloc becomes available. + AsmJSParallelTask *task = GetFinishedCompilation(m, group); + if (!task) + return false; + + // Perform code generation on the main thread. + if (!GenerateAsmJSCode(m, m.function(task->funcNum), *task->mir, *task->lir)) + return false; + group.compiledJobs++; + + // Clear the LifoAlloc for use by another worker. + TempAllocator &tempAlloc = task->mir->temp(); + tempAlloc.TempAllocator::~TempAllocator(); + task->lifo.releaseAll(); + + *outTask = task; + return true; +} + +static inline bool +GetUnusedTask(ParallelGroupState &group, uint32_t funcNum, AsmJSParallelTask **outTask) +{ + // Since functions are dispatched in order, if fewer than |numLifos| functions + // have been generated, then the |funcNum'th| LifoAlloc must never have been + // assigned to a worker thread. + if (funcNum >= group.tasks.length()) + return false; + *outTask = &group.tasks[funcNum]; + return true; +} + +static bool +CheckFunctionBodiesParallelImpl(ModuleCompiler &m, ParallelGroupState &group) +{ + JS_ASSERT(group.state.asmJSWorklist.empty()); + JS_ASSERT(group.state.asmJSFinishedList.empty()); + group.state.resetAsmJSFailureState(); + + // Dispatch work for each function. + for (uint32_t i = 0; i < m.numFunctions(); i++) { + ModuleCompiler::Func &func = m.function(i); + + // Get exclusive access to an empty LifoAlloc from the thread group's pool. + AsmJSParallelTask *task = NULL; + if (!GetUnusedTask(group, i, &task) && !GenerateCodeForFinishedJob(m, group, &task)) + return false; + + // Generate MIR into the LifoAlloc on the main thread. + MIRGenerator *mir = CheckFunctionBody(m, func, task->lifo); + if (!mir) + return false; + + // Perform optimizations and LIR generation on a worker thread. + task->init(i, mir); + if (!StartOffThreadAsmJSCompile(m.cx(), task)) + return false; + + group.outstandingJobs++; + } + + // Block for all outstanding workers to complete. + while (group.outstandingJobs > 0) { + AsmJSParallelTask *ignored = NULL; + if (!GenerateCodeForFinishedJob(m, group, &ignored)) + return false; + } + + 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()); + + return true; +} + +static void +CancelOutstandingJobs(ModuleCompiler &m, ParallelGroupState &group) +{ + // This is failure-handling code, so it's not allowed to fail. + // The problem is that all memory for compilation is stored in LifoAllocs + // maintained in the scope of CheckFunctionBodiesParallel() -- so in order + // for that function to safely return, and thereby remove the LifoAllocs, + // none of that memory can be in use or reachable by workers. + + JS_ASSERT(group.outstandingJobs >= 0); + if (!group.outstandingJobs) + return; + + AutoLockWorkerThreadState lock(m.cx()->runtime); + + // From the compiling tasks, eliminate those waiting for worker assignation. + group.outstandingJobs -= group.state.asmJSWorklist.length(); + group.state.asmJSWorklist.clear(); + + // From the compiling tasks, eliminate those waiting for codegen. + group.outstandingJobs -= group.state.asmJSFinishedList.length(); + group.state.asmJSFinishedList.clear(); + + // Eliminate tasks that failed without adding to the finished list. + group.outstandingJobs -= group.state.harvestFailedAsmJSJobs(); + + // Any remaining tasks are therefore undergoing active compilation. + JS_ASSERT(group.outstandingJobs >= 0); + while (group.outstandingJobs > 0) { + group.state.wait(WorkerThreadState::MAIN); + + group.outstandingJobs -= group.state.harvestFailedAsmJSJobs(); + group.outstandingJobs -= group.state.asmJSFinishedList.length(); + group.state.asmJSFinishedList.clear(); + } + + JS_ASSERT(group.outstandingJobs == 0); + JS_ASSERT(group.state.asmJSWorklist.empty()); + JS_ASSERT(group.state.asmJSFinishedList.empty()); +} + +static const size_t LIFO_ALLOC_PARALLEL_CHUNK_SIZE = 1 << 12; + +static bool +CheckFunctionBodiesParallel(ModuleCompiler &m) +{ + // Saturate all worker threads plus the main thread. + WorkerThreadState &state = *m.cx()->runtime->workerThreadState; + size_t numParallelJobs = state.numThreads + 1; + + // Allocate scoped AsmJSParallelTask objects. Each contains a unique + // LifoAlloc that provides all necessary memory for compilation. + Vector tasks(m.cx()); + if (!tasks.initCapacity(numParallelJobs)) + return false; + + for (size_t i = 0; i < numParallelJobs; i++) + tasks.infallibleAppend(LIFO_ALLOC_PARALLEL_CHUNK_SIZE); + + // With compilation memory in-scope, dispatch worker threads. + ParallelGroupState group(state, tasks); + if (!CheckFunctionBodiesParallelImpl(m, group)) { + CancelOutstandingJobs(m, group); + + // If failure was triggered by a worker thread, report error. + int32_t maybeFailureIndex = state.maybeGetAsmJSFailedFunctionIndex(); + if (maybeFailureIndex >= 0) { + ParseNode *fn = m.function(maybeFailureIndex).fn(); + return m.fail("Internal compiler failure (probably out of memory)", fn); + } + + // Otherwise, the error occurred on the main thread and was already reported. + return false; + } + return true; +} +#endif // JS_PARALLEL_COMPILATION + static RegisterSet AllRegs = RegisterSet(GeneralRegisterSet(Registers::AllMask), FloatRegisterSet(FloatRegisters::AllMask)); static RegisterSet NonVolatileRegs = RegisterSet(GeneralRegisterSet(Registers::NonVolatileMask), @@ -4958,8 +5148,18 @@ CheckModule(JSContext *cx, TokenStream &ts, ParseNode *fn, ScopedJSDeletePtrruntime)) return Warn(cx, JSMSG_USE_ASM_TYPE_FAIL, "Platform missing signal handler support"); +# ifdef JS_PARALLEL_COMPILATION + if (!EnsureParallelCompilationInitialized(cx->runtime)) + return Warn(cx, JSMSG_USE_ASM_TYPE_FAIL, "Failed initialization of compilation threads"); +# endif + ScopedJSDeletePtr module; if (!CheckModule(cx, ts, fn, &module)) return !cx->isExceptionPending(); diff --git a/js/src/ion/AsmJS.h b/js/src/ion/AsmJS.h index 7ae1c5e6e74..70451452139 100644 --- a/js/src/ion/AsmJS.h +++ b/js/src/ion/AsmJS.h @@ -27,6 +27,7 @@ namespace js { class SPSProfiler; class AsmJSModule; namespace frontend { struct TokenStream; struct ParseNode; } +namespace ion { class MIRGenerator; class LIRGraph; } // Return whether asm.js optimization is inhibitted by the platform or // dynamically disabled. (Exposed as JSNative for shell testing.) @@ -125,6 +126,28 @@ class AsmJSMachExceptionHandler }; #endif +// Struct type for passing parallel compilation data between the main thread +// and compilation workers. +struct AsmJSParallelTask +{ + LifoAlloc lifo; // Provider of all heap memory used for compilation. + + uint32_t funcNum; // Index |i| of function in |Module.function(i)|. + ion::MIRGenerator *mir; // Passed from main thread to worker. + ion::LIRGraph *lir; // Passed from worker to main thread. + + AsmJSParallelTask(size_t defaultChunkSize) + : lifo(defaultChunkSize), + funcNum(0), mir(NULL), lir(NULL) + { } + + void init(uint32_t newFuncNum, ion::MIRGenerator *newMir) { + funcNum = newFuncNum; + mir = newMir; + lir = NULL; + } +}; + } // namespace js #endif // jsion_asmjs_h__ diff --git a/js/src/ion/Ion.cpp b/js/src/ion/Ion.cpp index b4a59272ae6..52dc9c50751 100644 --- a/js/src/ion/Ion.cpp +++ b/js/src/ion/Ion.cpp @@ -197,7 +197,7 @@ IonRuntime::initialize(JSContext *cx) FrameSizeClass class_ = FrameSizeClass::FromClass(id); if (class_ == FrameSizeClass::ClassLimit()) break; - bailoutTables_.infallibleAppend(NULL); + bailoutTables_.infallibleAppend((IonCode *)NULL); bailoutTables_[id] = generateBailoutTable(cx, id); if (!bailoutTables_[id]) return false; @@ -1261,14 +1261,6 @@ IonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, return abortReason; } -static inline bool -OffThreadCompilationEnabled(JSContext *cx) -{ - return js_IonOptions.parallelCompilation - && cx->runtime->useHelperThreads() - && cx->runtime->helperThreadCount() != 0; -} - static inline bool OffThreadCompilationAvailable(JSContext *cx) { diff --git a/js/src/ion/IonMacroAssembler.h b/js/src/ion/IonMacroAssembler.h index 0c8647eabb3..d7c173e660a 100644 --- a/js/src/ion/IonMacroAssembler.h +++ b/js/src/ion/IonMacroAssembler.h @@ -80,8 +80,11 @@ class MacroAssembler : public MacroAssemblerSpecific if (cx) constructRoot(cx); - if (!GetIonContext()->temp) + if (!GetIonContext()->temp) { + JS_ASSERT(cx); alloc_.construct(cx); + } + #ifdef JS_CPU_ARM initWithAllocator(); m_buffer.id = GetIonContext()->getNextAssemblerId(); diff --git a/js/src/ion/RegisterAllocator.cpp b/js/src/ion/RegisterAllocator.cpp index ade90330948..f9986453416 100644 --- a/js/src/ion/RegisterAllocator.cpp +++ b/js/src/ion/RegisterAllocator.cpp @@ -25,7 +25,7 @@ AllocationIntegrityState::record() if (!virtualRegisters.reserve(graph.numVirtualRegisters())) return false; for (size_t i = 0; i < graph.numVirtualRegisters(); i++) - virtualRegisters.infallibleAppend(NULL); + virtualRegisters.infallibleAppend((LDefinition *)NULL); if (!blocks.reserve(graph.numBlocks())) return false; diff --git a/js/src/ion/StupidAllocator.cpp b/js/src/ion/StupidAllocator.cpp index b5b3fb7543f..c0646ac78f2 100644 --- a/js/src/ion/StupidAllocator.cpp +++ b/js/src/ion/StupidAllocator.cpp @@ -50,7 +50,7 @@ StupidAllocator::init() if (!virtualRegisters.reserve(graph.numVirtualRegisters())) return false; for (size_t i = 0; i < graph.numVirtualRegisters(); i++) - virtualRegisters.infallibleAppend(NULL); + virtualRegisters.infallibleAppend((LDefinition *)NULL); for (size_t i = 0; i < graph.numBlocks(); i++) { LBlock *block = graph.getBlock(i); diff --git a/js/src/ion/shared/Assembler-shared.h b/js/src/ion/shared/Assembler-shared.h index 16fbed99823..37b6fc833f8 100644 --- a/js/src/ion/shared/Assembler-shared.h +++ b/js/src/ion/shared/Assembler-shared.h @@ -261,7 +261,7 @@ class Label : public LabelBase // Note: the condition is a hack to silence this assert when OOM testing, // see bug 756614. if (!js_IonOptions.parallelCompilation) - JS_ASSERT_IF(!GetIonContext()->cx->runtime->hadOutOfMemory, !used()); + JS_ASSERT_IF(!GetIonContext()->runtime->hadOutOfMemory, !used()); #endif } }; diff --git a/js/src/jsworkers.cpp b/js/src/jsworkers.cpp index d0212776bc1..d80ee15f303 100644 --- a/js/src/jsworkers.cpp +++ b/js/src/jsworkers.cpp @@ -9,6 +9,7 @@ #include "jsworkers.h" #if JS_ION +# include "ion/AsmJS.h" # include "ion/IonBuilder.h" # include "ion/ExecutionModeInlines.h" #endif @@ -19,22 +20,57 @@ 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, ion::IonBuilder *builder) { JSRuntime *rt = cx->runtime; - if (!rt->workerThreadState) { - rt->workerThreadState = rt->new_(); - if (!rt->workerThreadState) - return false; - if (!rt->workerThreadState->init(rt)) { - js_delete(rt->workerThreadState); - rt->workerThreadState = NULL; - return false; - } - } - WorkerThreadState &state = *cx->runtime->workerThreadState; + if (!EnsureParallelCompilationInitialized(rt)) + return false; + WorkerThreadState &state = *cx->runtime->workerThreadState; JS_ASSERT(state.numThreads); AutoLockWorkerThreadState lock(rt); @@ -43,7 +79,6 @@ js::StartOffThreadIonCompile(JSContext *cx, ion::IonBuilder *builder) return false; state.notify(WorkerThreadState::WORKER); - return true; } @@ -162,6 +197,7 @@ WorkerThreadState::init(JSRuntime *rt) } } + resetAsmJSFailureState(); return true; } @@ -245,6 +281,14 @@ WorkerThreadState::notifyAll(CondVar which) 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() { @@ -288,6 +332,77 @@ WorkerThread::ThreadMain(void *arg) 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 { + 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 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() { @@ -298,9 +413,10 @@ WorkerThread::threadLoop() js::TlsPerThreadData.set(threadData.addr()); while (true) { - JS_ASSERT(!ionBuilder); + JS_ASSERT(!ionBuilder && !asmData); - while (!state.canStartIonCompile()) { + // Block until an Ion or AsmJS task is available. + while (!state.canStartIonCompile() && !state.canStartAsmJSCompile()) { if (terminate) { state.unlock(); return; @@ -308,39 +424,23 @@ WorkerThread::threadLoop() state.wait(WorkerThreadState::WORKER); } - ionBuilder = state.ionWorklist.popCopy(); - - DebugOnly 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(); + // 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) { diff --git a/js/src/jsworkers.h b/js/src/jsworkers.h index 04f76b08359..8c3b142dc81 100644 --- a/js/src/jsworkers.h +++ b/js/src/jsworkers.h @@ -18,16 +18,27 @@ #include "jscntxt.h" #include "jslock.h" +#include "ion/Ion.h" + namespace js { namespace ion { class IonBuilder; } +inline bool +OffThreadCompilationEnabled(JSContext *cx) +{ + return ion::js_IonOptions.parallelCompilation + && cx->runtime->useHelperThreads() + && cx->runtime->helperThreadCount() != 0; +} + #if defined(JS_THREADSAFE) && defined(JS_ION) # define JS_PARALLEL_COMPILATION struct WorkerThread; +struct AsmJSParallelTask; /* Per-runtime state for off thread work items. */ class WorkerThreadState @@ -42,9 +53,19 @@ class WorkerThreadState WORKER }; - /* Shared worklist for helper threads. */ + /* Shared worklist for Ion worker threads. */ js::Vector ionWorklist; + /* Worklist for AsmJS worker threads. */ + js::Vector 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. + */ + js::Vector asmJSFinishedList; + WorkerThreadState() { PodZero(this); } ~WorkerThreadState(); @@ -61,8 +82,33 @@ class WorkerThreadState void notify(CondVar which); void notifyAll(CondVar which); + bool canStartAsmJSCompile(); bool canStartIonCompile(); + uint32_t harvestFailedAsmJSJobs() { + JS_ASSERT(isLocked()); + uint32_t n = numAsmJSFailedJobs; + numAsmJSFailedJobs = 0; + return n; + } + void noteAsmJSFailure(int32_t func) { + // Be mindful to signal the main thread after calling this function. + JS_ASSERT(isLocked()); + if (asmJSFailedFunctionIndex < 0) + asmJSFailedFunctionIndex = func; + numAsmJSFailedJobs++; + } + bool asmJSWorkerFailed() const { + return bool(numAsmJSFailedJobs); + } + void resetAsmJSFailureState() { + numAsmJSFailedJobs = 0; + asmJSFailedFunctionIndex = -1; + } + int32_t maybeGetAsmJSFailedFunctionIndex() const { + return asmJSFailedFunctionIndex; + } + private: /* @@ -80,6 +126,18 @@ class WorkerThreadState /* Condvar to notify helper threads that they may be able to make progress. */ PRCondVar *helperWakeup; + + /* + * Number of AsmJS workers that encountered failure for the active module. + * Their parent is logically the main thread, and this number serves for harvesting. + */ + uint32_t numAsmJSFailedJobs; + + /* + * Function index |i| in |Module.function(i)| of first failed AsmJS function. + * -1 if no function has failed. + */ + int32_t asmJSFailedFunctionIndex; }; /* Individual helper thread, one allocated per core. */ @@ -96,8 +154,14 @@ struct WorkerThread /* Any builder currently being compiled by Ion on this thread. */ ion::IonBuilder *ionBuilder; + /* Any AsmJS data currently being optimized by Ion on this thread. */ + AsmJSParallelTask *asmData; + void destroy(); + void handleAsmJSWorkload(WorkerThreadState &state); + void handleIonWorkload(WorkerThreadState &state); + static void ThreadMain(void *arg); void threadLoop(); }; @@ -106,6 +170,14 @@ struct WorkerThread /* Methods for interacting with worker threads. */ +/* Initialize worker threads unless already initialized. */ +bool +EnsureParallelCompilationInitialized(JSRuntime *rt); + +/* Perform MIR optimization and LIR generation on a single function. */ +bool +StartOffThreadAsmJSCompile(JSContext *cx, AsmJSParallelTask *asmData); + /* * Schedule an Ion compilation for a script, given a builder which has been * generated and read everything needed from the VM state.