Bug 1379814 - Clean up task selection logic, implement master task concept (take 2). r=luke

--HG--
extra : rebase_source : 2e865028e8921e5043dabab0acce2bb915b1256c
extra : histedit_source : f093356241f3fd5e2681e517e1c66f660f715503
This commit is contained in:
Lars T Hansen 2017-08-11 10:56:54 +02:00
Родитель 4be438e52b
Коммит 38b8fb111d
2 изменённых файлов: 110 добавлений и 41 удалений

Просмотреть файл

@ -963,21 +963,56 @@ GlobalHelperThreadState::waitForAllThreads()
wait(lock, CONSUMER); wait(lock, CONSUMER);
} }
// A task can be a "master" task, ie, it will block waiting for other worker
// threads that perform work on its behalf. If so it must not take the last
// available thread; there must always be at least one worker thread able to do
// the actual work. (Or the system may deadlock.)
//
// If a task is a master task it *must* pass isMaster=true here, or perform a
// similar calculation to avoid deadlock from starvation.
//
// isMaster should only be true if the thread calling checkTaskThreadLimit() is
// a helper thread.
//
// NOTE: Calling checkTaskThreadLimit() from a helper thread in the dynamic
// region after currentTask.emplace() and before currentTask.reset() may cause
// it to return a different result than if it is called outside that dynamic
// region, as the predicate inspects the values of the threads' currentTask
// members.
template <typename T> template <typename T>
bool bool
GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads) const GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads, bool isMaster) const
{ {
if (maxThreads >= threadCount) MOZ_ASSERT(maxThreads > 0);
if (!isMaster && maxThreads >= threadCount)
return true; return true;
size_t count = 0; size_t count = 0;
size_t idle = 0;
for (auto& thread : *threads) { for (auto& thread : *threads) {
if (thread.currentTask.isSome() && thread.currentTask->is<T>()) if (thread.currentTask.isSome()) {
count++; if (thread.currentTask->is<T>())
count++;
} else {
idle++;
}
if (count >= maxThreads) if (count >= maxThreads)
return false; return false;
} }
// It is possible for the number of idle threads to be zero here, because
// checkTaskThreadLimit() can be called from non-helper threads. Notably,
// the compression task scheduler invokes it, and runs off a helper thread.
if (idle == 0)
return false;
// A master thread that's the last available thread must not be allowed to
// run.
if (isMaster && idle == 1)
return false;
return true; return true;
} }
@ -1213,7 +1248,12 @@ GlobalHelperThreadState::pendingIonCompileHasSufficientPriority(
bool bool
GlobalHelperThreadState::canStartParseTask(const AutoLockHelperThreadState& lock) GlobalHelperThreadState::canStartParseTask(const AutoLockHelperThreadState& lock)
{ {
return !parseWorklist(lock).empty() && checkTaskThreadLimit<ParseTask*>(maxParseThreads()); // Parse tasks that end up compiling asm.js in turn may use Wasm compilation
// threads to generate machine code. We have no way (at present) to know
// ahead of time whether a parse task is going to parse asm.js content or
// not, so we just assume that all parse tasks are master tasks.
return !parseWorklist(lock).empty() &&
checkTaskThreadLimit<ParseTask*>(maxParseThreads(), /*isMaster=*/true);
} }
bool bool
@ -2142,53 +2182,74 @@ HelperThread::threadLoop()
while (true) { while (true) {
MOZ_ASSERT(idle()); MOZ_ASSERT(idle());
// Block until a task is available. Save the value of whether we are js::oom::ThreadType task;
// going to do an Ion compile, in case the value returned by the method
// changes.
bool ionCompile = false;
while (true) { while (true) {
if (terminate) if (terminate)
return; return;
if ((ionCompile = HelperThreadState().pendingIonCompileHasSufficientPriority(lock)) ||
HelperThreadState().canStartWasmCompile(lock) || // Select the task type to run. Task priority is determined
HelperThreadState().canStartPromiseTask(lock) || // exclusively here.
HelperThreadState().canStartParseTask(lock) || //
HelperThreadState().canStartCompressionTask(lock) || // The selectors may depend on the HelperThreadState not changing
HelperThreadState().canStartGCHelperTask(lock) || // between task selection and task execution, in particular, on new
HelperThreadState().canStartGCParallelTask(lock) || // tasks not being added (because of the lifo structure of the work
HelperThreadState().canStartIonFreeTask(lock)) // lists). Unlocking the HelperThreadState between task selection
{ // and execution is not well-defined.
if (HelperThreadState().canStartGCParallelTask(lock))
task = js::oom::THREAD_TYPE_GCPARALLEL;
else if (HelperThreadState().canStartGCHelperTask(lock))
task = js::oom::THREAD_TYPE_GCHELPER;
else if (HelperThreadState().pendingIonCompileHasSufficientPriority(lock))
task = js::oom::THREAD_TYPE_ION;
else if (HelperThreadState().canStartWasmCompile(lock))
task = js::oom::THREAD_TYPE_WASM;
else if (HelperThreadState().canStartPromiseTask(lock))
task = js::oom::THREAD_TYPE_PROMISE_TASK;
else if (HelperThreadState().canStartParseTask(lock))
task = js::oom::THREAD_TYPE_PARSE;
else if (HelperThreadState().canStartCompressionTask(lock))
task = js::oom::THREAD_TYPE_COMPRESS;
else if (HelperThreadState().canStartIonFreeTask(lock))
task = js::oom::THREAD_TYPE_ION_FREE;
else
task = js::oom::THREAD_TYPE_NONE;
if (task != js::oom::THREAD_TYPE_NONE)
break; break;
}
HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER); HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER);
} }
if (HelperThreadState().canStartGCParallelTask(lock)) { js::oom::SetThreadType(task);
js::oom::SetThreadType(js::oom::THREAD_TYPE_GCPARALLEL); switch (task) {
case js::oom::THREAD_TYPE_GCPARALLEL:
handleGCParallelWorkload(lock); handleGCParallelWorkload(lock);
} else if (HelperThreadState().canStartGCHelperTask(lock)) { break;
js::oom::SetThreadType(js::oom::THREAD_TYPE_GCHELPER); case js::oom::THREAD_TYPE_GCHELPER:
handleGCHelperWorkload(lock); handleGCHelperWorkload(lock);
} else if (ionCompile) { break;
js::oom::SetThreadType(js::oom::THREAD_TYPE_ION); case js::oom::THREAD_TYPE_ION:
handleIonWorkload(lock); handleIonWorkload(lock);
} else if (HelperThreadState().canStartWasmCompile(lock)) { break;
js::oom::SetThreadType(js::oom::THREAD_TYPE_WASM); case js::oom::THREAD_TYPE_WASM:
handleWasmWorkload(lock); handleWasmWorkload(lock);
} else if (HelperThreadState().canStartPromiseTask(lock)) { break;
js::oom::SetThreadType(js::oom::THREAD_TYPE_PROMISE_TASK); case js::oom::THREAD_TYPE_PROMISE_TASK:
handlePromiseTaskWorkload(lock); handlePromiseTaskWorkload(lock);
} else if (HelperThreadState().canStartParseTask(lock)) { break;
js::oom::SetThreadType(js::oom::THREAD_TYPE_PARSE); case js::oom::THREAD_TYPE_PARSE:
handleParseWorkload(lock); handleParseWorkload(lock);
} else if (HelperThreadState().canStartCompressionTask(lock)) { break;
js::oom::SetThreadType(js::oom::THREAD_TYPE_COMPRESS); case js::oom::THREAD_TYPE_COMPRESS:
handleCompressionWorkload(lock); handleCompressionWorkload(lock);
} else if (HelperThreadState().canStartIonFreeTask(lock)) { break;
js::oom::SetThreadType(js::oom::THREAD_TYPE_ION_FREE); case js::oom::THREAD_TYPE_ION_FREE:
handleIonFreeWorkload(lock); handleIonFreeWorkload(lock);
} else { break;
default:
MOZ_CRASH("No task to perform"); MOZ_CRASH("No task to perform");
} }
js::oom::SetThreadType(js::oom::THREAD_TYPE_NONE);
} }
} }

Просмотреть файл

@ -146,10 +146,14 @@ class GlobalHelperThreadState
#endif #endif
enum CondVar { enum CondVar {
// For notifying threads waiting for work that they may be able to make progress. // For notifying threads waiting for work that they may be able to make
// progress, ie, a work item has been completed by a helper thread and
// the thread that created the work item can now consume it.
CONSUMER, CONSUMER,
// For notifying threads doing work that they may be able to make progress. // For notifying helper threads doing the work that they may be able to
// make progress, ie, a work item has been enqueued and an idle helper
// thread may pick up up the work item and perform it.
PRODUCER, PRODUCER,
// For notifying threads doing work which are paused that they may be // For notifying threads doing work which are paused that they may be
@ -235,10 +239,14 @@ class GlobalHelperThreadState
// Used by a major GC to signal processing enqueued compression tasks. // Used by a major GC to signal processing enqueued compression tasks.
void startHandlingCompressionTasks(const AutoLockHelperThreadState&); void startHandlingCompressionTasks(const AutoLockHelperThreadState&);
private:
void scheduleCompressionTasks(const AutoLockHelperThreadState&); void scheduleCompressionTasks(const AutoLockHelperThreadState&);
// Unlike the methods above, the value returned by this method can change public:
// over time, even if the helper thread state lock is held throughout. // Unlike the public methods above, the value returned by this method can
// change over time, even if the helper thread state lock is held
// throughout.
bool pendingIonCompileHasSufficientPriority(const AutoLockHelperThreadState& lock); bool pendingIonCompileHasSufficientPriority(const AutoLockHelperThreadState& lock);
jit::IonBuilder* highestPriorityPendingIonCompile(const AutoLockHelperThreadState& lock, jit::IonBuilder* highestPriorityPendingIonCompile(const AutoLockHelperThreadState& lock,
@ -310,7 +318,7 @@ class GlobalHelperThreadState
void waitForAllThreads(); void waitForAllThreads();
template <typename T> template <typename T>
bool checkTaskThreadLimit(size_t maxThreads) const; bool checkTaskThreadLimit(size_t maxThreads, bool isMaster = false) const;
private: private: