mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1013172 - IonMonkey: Implement compilation priority, r=jandem,luke
This commit is contained in:
parent
2be09c48f8
commit
b60e363f08
@ -5672,8 +5672,8 @@ CheckFunctionsParallel(ModuleCompiler &m)
|
||||
|
||||
IonSpew(IonSpew_Logs, "Can't log asm.js script. (Compiled on background thread.)");
|
||||
|
||||
// Saturate all helper threads plus the main thread.
|
||||
size_t numParallelJobs = HelperThreadState().threadCount + 1;
|
||||
// Saturate all helper threads.
|
||||
size_t numParallelJobs = HelperThreadState().maxAsmJSCompilationThreads();
|
||||
|
||||
// Allocate scoped AsmJSParallelTask objects. Each contains a unique
|
||||
// LifoAlloc that provides all necessary memory for compilation.
|
||||
|
@ -81,12 +81,21 @@ class MIRGenerator
|
||||
|
||||
// Whether the main thread is trying to cancel this build.
|
||||
bool shouldCancel(const char *why) {
|
||||
maybePause();
|
||||
return cancelBuild_;
|
||||
}
|
||||
void cancel() {
|
||||
cancelBuild_ = true;
|
||||
}
|
||||
|
||||
void maybePause() {
|
||||
if (pauseBuild_ && *pauseBuild_)
|
||||
PauseCurrentHelperThread();
|
||||
}
|
||||
void setPauseFlag(mozilla::Atomic<bool, mozilla::Relaxed> *pauseBuild) {
|
||||
pauseBuild_ = pauseBuild;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
abortReason_ = AbortReason_Disable;
|
||||
}
|
||||
@ -153,6 +162,7 @@ class MIRGenerator
|
||||
MIRGraph *graph_;
|
||||
AbortReason abortReason_;
|
||||
bool error_;
|
||||
mozilla::Atomic<bool, mozilla::Relaxed> *pauseBuild_;
|
||||
mozilla::Atomic<bool, mozilla::Relaxed> cancelBuild_;
|
||||
|
||||
uint32_t maxAsmJSStackArgBytes_;
|
||||
|
@ -26,6 +26,7 @@ MIRGenerator::MIRGenerator(CompileCompartment *compartment, const JitCompileOpti
|
||||
graph_(graph),
|
||||
abortReason_(AbortReason_NoAbort),
|
||||
error_(false),
|
||||
pauseBuild_(nullptr),
|
||||
cancelBuild_(false),
|
||||
maxAsmJSStackArgBytes_(0),
|
||||
performsCall_(false),
|
||||
|
@ -48,7 +48,10 @@ js::EnsureHelperThreadsInitialized(ExclusiveContext *cx)
|
||||
static size_t
|
||||
ThreadCountForCPUCount(size_t cpuCount)
|
||||
{
|
||||
return Max(cpuCount, (size_t)2);
|
||||
// Create additional threads on top of the number of cores available, to
|
||||
// provide some excess capacity in case threads pause each other.
|
||||
static const uint32_t EXCESS_THREADS = 4;
|
||||
return cpuCount + EXCESS_THREADS;
|
||||
}
|
||||
|
||||
void
|
||||
@ -143,11 +146,15 @@ js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
|
||||
|
||||
/* Wait for in progress entries to finish up. */
|
||||
for (size_t i = 0; i < HelperThreadState().threadCount; i++) {
|
||||
const HelperThread &helper = HelperThreadState().threads[i];
|
||||
HelperThread &helper = HelperThreadState().threads[i];
|
||||
while (helper.ionBuilder &&
|
||||
CompiledScriptMatches(compartment, script, helper.ionBuilder->script()))
|
||||
{
|
||||
helper.ionBuilder->cancel();
|
||||
if (helper.pause) {
|
||||
helper.pause = false;
|
||||
HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE);
|
||||
}
|
||||
HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
|
||||
}
|
||||
}
|
||||
@ -456,6 +463,7 @@ GlobalHelperThreadState::GlobalHelperThreadState()
|
||||
helperLock = PR_NewLock();
|
||||
consumerWakeup = PR_NewCondVar(helperLock);
|
||||
producerWakeup = PR_NewCondVar(helperLock);
|
||||
pauseWakeup = PR_NewCondVar(helperLock);
|
||||
}
|
||||
|
||||
void
|
||||
@ -469,6 +477,7 @@ GlobalHelperThreadState::finish()
|
||||
|
||||
PR_DestroyCondVar(consumerWakeup);
|
||||
PR_DestroyCondVar(producerWakeup);
|
||||
PR_DestroyCondVar(pauseWakeup);
|
||||
PR_DestroyLock(helperLock);
|
||||
}
|
||||
|
||||
@ -509,7 +518,7 @@ GlobalHelperThreadState::wait(CondVar which, uint32_t millis)
|
||||
lockOwner = nullptr;
|
||||
#endif
|
||||
DebugOnly<PRStatus> status =
|
||||
PR_WaitCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup,
|
||||
PR_WaitCondVar(whichWakeup(which),
|
||||
millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
|
||||
JS_ASSERT(status == PR_SUCCESS);
|
||||
#ifdef DEBUG
|
||||
@ -521,14 +530,14 @@ void
|
||||
GlobalHelperThreadState::notifyAll(CondVar which)
|
||||
{
|
||||
JS_ASSERT(isLocked());
|
||||
PR_NotifyAllCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
|
||||
PR_NotifyAllCondVar(whichWakeup(which));
|
||||
}
|
||||
|
||||
void
|
||||
GlobalHelperThreadState::notifyOne(CondVar which)
|
||||
{
|
||||
JS_ASSERT(isLocked());
|
||||
PR_NotifyCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
|
||||
PR_NotifyCondVar(whichWakeup(which));
|
||||
}
|
||||
|
||||
bool
|
||||
@ -536,23 +545,135 @@ GlobalHelperThreadState::canStartAsmJSCompile()
|
||||
{
|
||||
// Don't execute an AsmJS job if an earlier one failed.
|
||||
JS_ASSERT(isLocked());
|
||||
return !asmJSWorklist().empty() && !numAsmJSFailedJobs;
|
||||
if (asmJSWorklist().empty() || numAsmJSFailedJobs)
|
||||
return false;
|
||||
|
||||
// Honor the maximum allowed threads to compile AsmJS jobs at once,
|
||||
// to avoid oversaturating the machine.
|
||||
size_t numAsmJSThreads = 0;
|
||||
for (size_t i = 0; i < threadCount; i++) {
|
||||
if (threads[i].asmData)
|
||||
numAsmJSThreads++;
|
||||
}
|
||||
if (numAsmJSThreads >= maxAsmJSCompilationThreads())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
IonBuilderHasHigherPriority(IonBuilder *first, IonBuilder *second)
|
||||
{
|
||||
// This method can return whatever it wants, though it really ought to be a
|
||||
// total order. The ordering is allowed to race (change on the fly), however.
|
||||
|
||||
// A lower optimization level indicates a higher priority.
|
||||
if (first->optimizationInfo().level() != second->optimizationInfo().level())
|
||||
return first->optimizationInfo().level() < second->optimizationInfo().level();
|
||||
|
||||
// A script without an IonScript has precedence on one with.
|
||||
if (first->script()->hasIonScript() != second->script()->hasIonScript())
|
||||
return !first->script()->hasIonScript();
|
||||
|
||||
// A higher useCount indicates a higher priority.
|
||||
return first->script()->getUseCount() > second->script()->getUseCount();
|
||||
}
|
||||
|
||||
bool
|
||||
GlobalHelperThreadState::canStartIonCompile()
|
||||
{
|
||||
// A helper thread can begin an Ion compilation if (a) there is some script
|
||||
// which is waiting to be compiled, and (b) no other helper 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 < threadCount; i++) {
|
||||
if (threads[i].ionBuilder)
|
||||
return false;
|
||||
return !ionWorklist().empty();
|
||||
}
|
||||
|
||||
jit::IonBuilder *
|
||||
GlobalHelperThreadState::highestPriorityPendingIonCompile(bool remove /* = false */)
|
||||
{
|
||||
JS_ASSERT(isLocked());
|
||||
|
||||
if (ionWorklist().empty()) {
|
||||
JS_ASSERT(!remove);
|
||||
return nullptr;
|
||||
}
|
||||
return true;
|
||||
|
||||
// Get the highest priority IonBuilder which has not started compilation yet.
|
||||
size_t index = 0;
|
||||
for (size_t i = 1; i < ionWorklist().length(); i++) {
|
||||
if (IonBuilderHasHigherPriority(ionWorklist()[i], ionWorklist()[index]))
|
||||
index = i;
|
||||
}
|
||||
jit::IonBuilder *builder = ionWorklist()[index];
|
||||
if (remove)
|
||||
ionWorklist().erase(&ionWorklist()[index]);
|
||||
return builder;
|
||||
}
|
||||
|
||||
HelperThread *
|
||||
GlobalHelperThreadState::lowestPriorityUnpausedIonCompileAtThreshold()
|
||||
{
|
||||
JS_ASSERT(isLocked());
|
||||
|
||||
// Get the lowest priority IonBuilder which has started compilation and
|
||||
// isn't paused, unless there are still fewer than the aximum number of
|
||||
// such builders permitted.
|
||||
size_t numBuilderThreads = 0;
|
||||
HelperThread *thread = nullptr;
|
||||
for (size_t i = 0; i < threadCount; i++) {
|
||||
if (threads[i].ionBuilder && !threads[i].pause) {
|
||||
numBuilderThreads++;
|
||||
if (!thread || IonBuilderHasHigherPriority(thread->ionBuilder, threads[i].ionBuilder))
|
||||
thread = &threads[i];
|
||||
}
|
||||
}
|
||||
if (numBuilderThreads < maxIonCompilationThreads())
|
||||
return nullptr;
|
||||
return thread;
|
||||
}
|
||||
|
||||
HelperThread *
|
||||
GlobalHelperThreadState::highestPriorityPausedIonCompile()
|
||||
{
|
||||
JS_ASSERT(isLocked());
|
||||
|
||||
// Get the highest priority IonBuilder which has started compilation but
|
||||
// which was subsequently paused.
|
||||
HelperThread *thread = nullptr;
|
||||
for (size_t i = 0; i < threadCount; i++) {
|
||||
if (threads[i].pause) {
|
||||
// Currently, only threads with IonBuilders can be paused.
|
||||
JS_ASSERT(threads[i].ionBuilder);
|
||||
if (!thread || IonBuilderHasHigherPriority(threads[i].ionBuilder, thread->ionBuilder))
|
||||
thread = &threads[i];
|
||||
}
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
|
||||
bool
|
||||
GlobalHelperThreadState::pendingIonCompileHasSufficientPriority()
|
||||
{
|
||||
JS_ASSERT(isLocked());
|
||||
|
||||
// Can't compile anything if there are no scripts to compile.
|
||||
if (!canStartIonCompile())
|
||||
return false;
|
||||
|
||||
// Count the number of threads currently compiling scripts, and look for
|
||||
// the thread with the lowest priority.
|
||||
HelperThread *lowestPriorityThread = lowestPriorityUnpausedIonCompileAtThreshold();
|
||||
|
||||
// If the number of threads building scripts is less than the maximum, the
|
||||
// compilation can start immediately.
|
||||
if (!lowestPriorityThread)
|
||||
return true;
|
||||
|
||||
// If there is a builder in the worklist with higher priority than some
|
||||
// builder currently being compiled, then that current compilation can be
|
||||
// paused, so allow the compilation.
|
||||
if (IonBuilderHasHigherPriority(highestPriorityPendingIonCompile(), lowestPriorityThread->ionBuilder))
|
||||
return true;
|
||||
|
||||
// Compilation will have to wait until one of the active compilations finishes.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
@ -815,24 +936,23 @@ HelperThread::handleIonWorkload()
|
||||
JS_ASSERT(HelperThreadState().canStartIonCompile());
|
||||
JS_ASSERT(idle());
|
||||
|
||||
// Find the ionBuilder with the script having the highest usecount.
|
||||
GlobalHelperThreadState::IonBuilderVector &ionWorklist = HelperThreadState().ionWorklist();
|
||||
size_t highest = 0;
|
||||
for (size_t i = 1; i < ionWorklist.length(); i++) {
|
||||
if (ionWorklist[i]->script()->getUseCount() >
|
||||
ionWorklist[highest]->script()->getUseCount())
|
||||
{
|
||||
highest = i;
|
||||
}
|
||||
}
|
||||
ionBuilder = ionWorklist[highest];
|
||||
// Find the IonBuilder in the worklist with the highest priority, and
|
||||
// remove it from the worklist.
|
||||
jit::IonBuilder *builder =
|
||||
HelperThreadState().highestPriorityPendingIonCompile(/* remove = */ true);
|
||||
|
||||
// Pop the top IonBuilder and move it to the original place of the
|
||||
// IonBuilder we took to start compiling. If both are the same, only pop.
|
||||
if (highest != ionWorklist.length() - 1)
|
||||
ionWorklist[highest] = ionWorklist.popCopy();
|
||||
else
|
||||
ionWorklist.popBack();
|
||||
// If there are now too many threads with active IonBuilders, indicate to
|
||||
// the one with the lowest priority that it should pause. Note that due to
|
||||
// builder priorities changing since pendingIonCompileHasSufficientPriority
|
||||
// was called, the builder we are pausing may actually be higher priority
|
||||
// than the one we are about to start. Oh well.
|
||||
if (HelperThread *other = HelperThreadState().lowestPriorityUnpausedIonCompileAtThreshold()) {
|
||||
JS_ASSERT(other->ionBuilder && !other->pause);
|
||||
other->pause = true;
|
||||
}
|
||||
|
||||
ionBuilder = builder;
|
||||
ionBuilder->setPauseFlag(&pause);
|
||||
|
||||
TraceLogger *logger = TraceLoggerForCurrentThread();
|
||||
AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, ionBuilder->script()));
|
||||
@ -852,6 +972,7 @@ HelperThread::handleIonWorkload()
|
||||
|
||||
FinishOffThreadIonCompile(ionBuilder);
|
||||
ionBuilder = nullptr;
|
||||
pause = false;
|
||||
|
||||
// Ping the main thread so that the compiled code can be incorporated
|
||||
// at the next interrupt callback. Don't interrupt Ion code for this, as
|
||||
@ -861,11 +982,59 @@ HelperThread::handleIonWorkload()
|
||||
|
||||
// Notify the main thread in case it is waiting for the compilation to finish.
|
||||
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
|
||||
|
||||
// When finishing Ion compilation jobs, we can start unpausing compilation
|
||||
// threads that were paused to restrict the number of active compilations.
|
||||
// Only unpause one at a time, to make sure we don't exceed the restriction.
|
||||
// Since threads are currently only paused for Ion compilations, this
|
||||
// strategy will eventually unpause all paused threads, regardless of how
|
||||
// many there are, since each thread we unpause will eventually finish and
|
||||
// end up back here.
|
||||
if (HelperThread *other = HelperThreadState().highestPriorityPausedIonCompile()) {
|
||||
JS_ASSERT(other->ionBuilder && other->pause);
|
||||
|
||||
// Only unpause the other thread if there isn't a higher priority
|
||||
// builder which this thread or another can start on.
|
||||
jit::IonBuilder *builder = HelperThreadState().highestPriorityPendingIonCompile();
|
||||
if (!builder || IonBuilderHasHigherPriority(other->ionBuilder, builder)) {
|
||||
other->pause = false;
|
||||
|
||||
// Notify all paused threads, to make sure the one we just
|
||||
// unpaused wakes up.
|
||||
HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
MOZ_CRASH();
|
||||
#endif // JS_ION
|
||||
}
|
||||
|
||||
static HelperThread *
|
||||
CurrentHelperThread()
|
||||
{
|
||||
PRThread *prThread = PR_GetCurrentThread();
|
||||
HelperThread *thread = nullptr;
|
||||
for (size_t i = 0; i < HelperThreadState().threadCount; i++) {
|
||||
if (prThread == HelperThreadState().threads[i].thread) {
|
||||
thread = &HelperThreadState().threads[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
JS_ASSERT(thread);
|
||||
return thread;
|
||||
}
|
||||
|
||||
void
|
||||
js::PauseCurrentHelperThread()
|
||||
{
|
||||
HelperThread *thread = CurrentHelperThread();
|
||||
|
||||
AutoLockHelperThreadState lock;
|
||||
while (thread->pause)
|
||||
HelperThreadState().wait(GlobalHelperThreadState::PAUSE);
|
||||
}
|
||||
|
||||
void
|
||||
ExclusiveContext::setHelperThread(HelperThread *thread)
|
||||
{
|
||||
@ -1070,14 +1239,17 @@ HelperThread::threadLoop()
|
||||
threadData.ref().nativeStackLimit[i] = stackLimit;
|
||||
|
||||
while (true) {
|
||||
JS_ASSERT(!ionBuilder && !asmData);
|
||||
JS_ASSERT(idle());
|
||||
|
||||
// Block until a task is available.
|
||||
// Block until a task is available. Save the value of whether we are
|
||||
// going to do an Ion compile, in case the value returned by the method
|
||||
// changes.
|
||||
bool ionCompile = false;
|
||||
while (true) {
|
||||
if (terminate)
|
||||
return;
|
||||
if (HelperThreadState().canStartIonCompile() ||
|
||||
HelperThreadState().canStartAsmJSCompile() ||
|
||||
if (HelperThreadState().canStartAsmJSCompile() ||
|
||||
(ionCompile = HelperThreadState().pendingIonCompileHasSufficientPriority()) ||
|
||||
HelperThreadState().canStartParseTask() ||
|
||||
HelperThreadState().canStartCompressionTask() ||
|
||||
HelperThreadState().canStartGCHelperTask())
|
||||
@ -1090,7 +1262,7 @@ HelperThread::threadLoop()
|
||||
// Dispatch tasks, prioritizing AsmJS work.
|
||||
if (HelperThreadState().canStartAsmJSCompile())
|
||||
handleAsmJSWorkload();
|
||||
else if (HelperThreadState().canStartIonCompile())
|
||||
else if (ionCompile)
|
||||
handleIonWorkload();
|
||||
else if (HelperThreadState().canStartParseTask())
|
||||
handleParseWorkload();
|
||||
@ -1166,4 +1338,10 @@ ExclusiveContext::addPendingOverRecursed()
|
||||
MOZ_ASSUME_UNREACHABLE("Off thread compilation not available.");
|
||||
}
|
||||
|
||||
void
|
||||
js::PauseCurrentHelperThread()
|
||||
{
|
||||
MOZ_ASSUME_UNREACHABLE("Off thread compilation not available.");
|
||||
}
|
||||
|
||||
#endif /* JS_THREADSAFE */
|
||||
|
@ -86,6 +86,15 @@ class GlobalHelperThreadState
|
||||
GCHelperStateVector gcHelperWorklist_;
|
||||
|
||||
public:
|
||||
size_t maxIonCompilationThreads() const {
|
||||
return 1;
|
||||
}
|
||||
size_t maxAsmJSCompilationThreads() const {
|
||||
if (cpuCount < 2)
|
||||
return 2;
|
||||
return cpuCount;
|
||||
}
|
||||
|
||||
GlobalHelperThreadState();
|
||||
|
||||
void ensureInitialized();
|
||||
@ -103,7 +112,11 @@ class GlobalHelperThreadState
|
||||
CONSUMER,
|
||||
|
||||
// For notifying threads doing work that they may be able to make progress.
|
||||
PRODUCER
|
||||
PRODUCER,
|
||||
|
||||
// For notifying threads doing work which are paused that they may be
|
||||
// able to resume making progress.
|
||||
PAUSE
|
||||
};
|
||||
|
||||
void wait(CondVar which, uint32_t timeoutMillis = 0);
|
||||
@ -165,6 +178,14 @@ class GlobalHelperThreadState
|
||||
bool canStartCompressionTask();
|
||||
bool canStartGCHelperTask();
|
||||
|
||||
// Unlike the methods above, the value returned by this method can change
|
||||
// over time, even if the helper thread state lock is held throughout.
|
||||
bool pendingIonCompileHasSufficientPriority();
|
||||
|
||||
jit::IonBuilder *highestPriorityPendingIonCompile(bool remove = false);
|
||||
HelperThread *lowestPriorityUnpausedIonCompileAtThreshold();
|
||||
HelperThread *highestPriorityPausedIonCompile();
|
||||
|
||||
uint32_t harvestFailedAsmJSJobs() {
|
||||
JS_ASSERT(isLocked());
|
||||
uint32_t n = numAsmJSFailedJobs;
|
||||
@ -207,6 +228,16 @@ class GlobalHelperThreadState
|
||||
/* Condvars for threads waiting/notifying each other. */
|
||||
PRCondVar *consumerWakeup;
|
||||
PRCondVar *producerWakeup;
|
||||
PRCondVar *pauseWakeup;
|
||||
|
||||
PRCondVar *whichWakeup(CondVar which) {
|
||||
switch (which) {
|
||||
case CONSUMER: return consumerWakeup;
|
||||
case PRODUCER: return producerWakeup;
|
||||
case PAUSE: return pauseWakeup;
|
||||
default: MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Number of AsmJS jobs that encountered failure for the active module.
|
||||
@ -234,9 +265,19 @@ struct HelperThread
|
||||
mozilla::Maybe<PerThreadData> threadData;
|
||||
PRThread *thread;
|
||||
|
||||
/* Indicate to an idle thread that it should finish executing. */
|
||||
/*
|
||||
* Indicate to a thread that it should terminate itself. This is only read
|
||||
* or written with the helper thread state lock held.
|
||||
*/
|
||||
bool terminate;
|
||||
|
||||
/*
|
||||
* Indicate to a thread that it should pause execution. This is only
|
||||
* written with the helper thread state lock held, but may be read from
|
||||
* without the lock held.
|
||||
*/
|
||||
mozilla::Atomic<bool, mozilla::Relaxed> pause;
|
||||
|
||||
/* Any builder currently being compiled by Ion on this thread. */
|
||||
jit::IonBuilder *ionBuilder;
|
||||
|
||||
@ -281,6 +322,10 @@ EnsureHelperThreadsInitialized(ExclusiveContext *cx);
|
||||
void
|
||||
SetFakeCPUCount(size_t count);
|
||||
|
||||
// Pause the current thread until it's pause flag is unset.
|
||||
void
|
||||
PauseCurrentHelperThread();
|
||||
|
||||
#ifdef JS_ION
|
||||
|
||||
/* Perform MIR optimization and LIR generation on a single function. */
|
||||
|
Loading…
Reference in New Issue
Block a user