mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 850070 - Part 2/2 - Parallelize OdinMonkey compilations. r=luke
This commit is contained in:
parent
d5179d8716
commit
08c26b02b9
@ -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 <class U> void internalAppend(U t);
|
||||
template <class U> void internalAppend(U u);
|
||||
void internalAppendN(const T &t, size_t n);
|
||||
template <class U> void internalAppend(const U *begin, size_t length);
|
||||
template <class U, size_t O, class BP> void internalAppend(const Vector<U,O,BP> &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 <class U> void infallibleAppend(const U &u) {
|
||||
internalAppend(u);
|
||||
}
|
||||
void infallibleAppendN(const T &t, size_t n) {
|
||||
internalAppendN(t, n);
|
||||
@ -699,6 +702,25 @@ Vector<T,N,AP>::growStorageBy(size_t incr)
|
||||
return Impl::growTo(*this, newCap);
|
||||
}
|
||||
|
||||
template <class T, size_t N, class AP>
|
||||
inline bool
|
||||
Vector<T,N,AP>::initCapacity(size_t capacity)
|
||||
{
|
||||
JS_ASSERT(empty());
|
||||
JS_ASSERT(usingInlineStorage());
|
||||
if (capacity == 0)
|
||||
return true;
|
||||
T *newbuf = reinterpret_cast<T *>(this->malloc_(capacity * sizeof(T)));
|
||||
if (!newbuf)
|
||||
return false;
|
||||
mBegin = newbuf;
|
||||
mCapacity = capacity;
|
||||
#ifdef DEBUG
|
||||
mReserved = capacity;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T, size_t N, class AP>
|
||||
inline bool
|
||||
Vector<T,N,AP>::reserve(size_t request)
|
||||
@ -837,11 +859,11 @@ Vector<T,N,AP>::append(U t)
|
||||
template <class T, size_t N, class AP>
|
||||
template <class U>
|
||||
JS_ALWAYS_INLINE void
|
||||
Vector<T,N,AP>::internalAppend(U t)
|
||||
Vector<T,N,AP>::internalAppend(U u)
|
||||
{
|
||||
JS_ASSERT(mLength + 1 <= mReserved);
|
||||
JS_ASSERT(mReserved <= mCapacity);
|
||||
new(endNoCheck()) T(t);
|
||||
new(endNoCheck()) T(u);
|
||||
++mLength;
|
||||
}
|
||||
|
||||
|
@ -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<AsmJSGlobalAccess> GlobalAccessVector;
|
||||
|
||||
JSContext * cx_;
|
||||
IonContext ionContext_;
|
||||
IonContext ictx_;
|
||||
MacroAssembler masm_;
|
||||
|
||||
ScopedJSDeletePtr<AsmJSModule> 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<AsmJSParallelTask> &tasks;
|
||||
int32_t outstandingJobs; // Good work, jobs!
|
||||
uint32_t compiledJobs;
|
||||
|
||||
ParallelGroupState(WorkerThreadState &state, Vector<AsmJSParallelTask> &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<AsmJSParallelTask, 0> 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, ScopedJSDeletePtr<Asm
|
||||
|
||||
m.setFirstPassComplete();
|
||||
|
||||
#ifdef JS_PARALLEL_COMPILATION
|
||||
if (OffThreadCompilationEnabled(cx)) {
|
||||
if (!CheckFunctionBodiesParallel(m))
|
||||
return false;
|
||||
} else {
|
||||
if (!CheckFunctionBodiesSequential(m))
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!CheckFunctionBodiesSequential(m))
|
||||
return false;
|
||||
#endif
|
||||
|
||||
m.setSecondPassComplete();
|
||||
|
||||
@ -5001,6 +5201,11 @@ js::CompileAsmJS(JSContext *cx, TokenStream &ts, ParseNode *fn, HandleScript scr
|
||||
if (!EnsureAsmJSSignalHandlersInstalled(cx->runtime))
|
||||
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<AsmJSModule> module;
|
||||
if (!CheckModule(cx, ts, fn, &module))
|
||||
return !cx->isExceptionPending();
|
||||
|
@ -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__
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
|
@ -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_<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 (!rt->workerThreadState) {
|
||||
rt->workerThreadState = rt->new_<WorkerThreadState>();
|
||||
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<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()
|
||||
{
|
||||
@ -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<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();
|
||||
// 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)
|
||||
{
|
||||
|
@ -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<ion::IonBuilder*, 0, SystemAllocPolicy> ionWorklist;
|
||||
|
||||
/* Worklist for AsmJS worker threads. */
|
||||
js::Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> 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<AsmJSParallelTask*, 0, SystemAllocPolicy> 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.
|
||||
|
Loading…
Reference in New Issue
Block a user