Bug 1866491 - Try to batch Ion compilation tasks to free in a single IonFreeTask. r=jonco

When we cancel Ion compilations, there can be a lot of tasks on the lazy-link list.
Instead of creating a separate `IonFreeTask` for each of those, we can add the
compilation tasks to a vector and then create a single `IonFreeTask` to free them.

On Speedometer 3 there are a few cases where we now batch more than 70 compilation
tasks into a single `IonFreeTask`.

Differential Revision: https://phabricator.services.mozilla.com/D194628
This commit is contained in:
Jan de Mooij 2023-11-27 12:05:56 +00:00
parent 1e8e8eb17c
commit 3caf9e82c0
6 changed files with 119 additions and 89 deletions

View file

@ -376,10 +376,8 @@ void jit::LinkIonScript(JSContext* cx, HandleScript calleeScript) {
} }
} }
{ AutoStartIonFreeTask freeTask;
AutoLockHelperThreadState lock; FinishOffThreadTask(cx->runtime(), freeTask, task);
FinishOffThreadTask(cx->runtime(), task, lock);
}
} }
uint8_t* jit::LazyLinkTopActivation(JSContext* cx, uint8_t* jit::LazyLinkTopActivation(JSContext* cx,

View file

@ -151,7 +151,7 @@ void jit::AttachFinishedCompilations(JSContext* cx) {
MOZ_ASSERT(!rt->jitRuntime()->numFinishedOffThreadTasks()); MOZ_ASSERT(!rt->jitRuntime()->numFinishedOffThreadTasks());
} }
void jit::FreeIonCompileTask(IonCompileTask* task) { static void FreeIonCompileTask(IonCompileTask* task) {
// The task is allocated into its LifoAlloc, so destroying that will // The task is allocated into its LifoAlloc, so destroying that will
// destroy the task and all other data accumulated during compilation, // destroy the task and all other data accumulated during compilation,
// except any final codegen (which includes an assembler and needs to be // except any final codegen (which includes an assembler and needs to be
@ -160,18 +160,27 @@ void jit::FreeIonCompileTask(IonCompileTask* task) {
js_delete(task->alloc().lifoAlloc()); js_delete(task->alloc().lifoAlloc());
} }
void jit::FreeIonCompileTasks(const IonFreeCompileTasks& tasks) {
MOZ_ASSERT(!tasks.empty());
for (auto* task : tasks) {
FreeIonCompileTask(task);
}
}
void IonFreeTask::runHelperThreadTask(AutoLockHelperThreadState& locked) { void IonFreeTask::runHelperThreadTask(AutoLockHelperThreadState& locked) {
{ {
AutoUnlockHelperThreadState unlock(locked); AutoUnlockHelperThreadState unlock(locked);
jit::FreeIonCompileTask(task_); jit::FreeIonCompileTasks(compileTasks());
} }
js_delete(this); js_delete(this);
} }
void jit::FinishOffThreadTask(JSRuntime* runtime, IonCompileTask* task, void jit::FinishOffThreadTask(JSRuntime* runtime,
const AutoLockHelperThreadState& locked) { AutoStartIonFreeTask& freeTask,
IonCompileTask* task) {
MOZ_ASSERT(runtime); MOZ_ASSERT(runtime);
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime));
JSScript* script = task->script(); JSScript* script = task->script();
@ -196,8 +205,9 @@ void jit::FinishOffThreadTask(JSRuntime* runtime, IonCompileTask* task,
} }
} }
// Free Ion LifoAlloc off-thread. Free on the main thread if this OOMs. // Try to free the Ion LifoAlloc off-thread. Free on the main thread if this
if (!StartOffThreadIonFree(task, locked)) { // OOMs.
if (!freeTask.appendCompileTask(task)) {
FreeIonCompileTask(task); FreeIonCompileTask(task);
} }
} }

View file

@ -67,21 +67,23 @@ class IonCompileTask final : public HelperThreadTask,
}; };
class IonFreeTask : public HelperThreadTask { class IonFreeTask : public HelperThreadTask {
IonFreeCompileTasks tasks_;
public: public:
explicit IonFreeTask(IonCompileTask* task) : task_(task) {} explicit IonFreeTask(IonFreeCompileTasks&& tasks) : tasks_(std::move(tasks)) {
IonCompileTask* compileTask() { return task_; } MOZ_ASSERT(!tasks_.empty());
}
const IonFreeCompileTasks& compileTasks() const { return tasks_; }
ThreadType threadType() override { return THREAD_TYPE_ION_FREE; } ThreadType threadType() override { return THREAD_TYPE_ION_FREE; }
void runHelperThreadTask(AutoLockHelperThreadState& locked) override; void runHelperThreadTask(AutoLockHelperThreadState& locked) override;
private:
IonCompileTask* task_;
}; };
void AttachFinishedCompilations(JSContext* cx); void AttachFinishedCompilations(JSContext* cx);
void FinishOffThreadTask(JSRuntime* runtime, IonCompileTask* task, void FinishOffThreadTask(JSRuntime* runtime, AutoStartIonFreeTask& freeTask,
const AutoLockHelperThreadState& lock); IonCompileTask* task);
void FreeIonCompileTask(IonCompileTask* task); void FreeIonCompileTasks(const IonFreeCompileTasks& tasks);
} // namespace jit } // namespace jit
} // namespace js } // namespace js

View file

@ -412,7 +412,7 @@ class GlobalHelperThreadState {
public: public:
bool submitTask(wasm::UniqueTier2GeneratorTask task); bool submitTask(wasm::UniqueTier2GeneratorTask task);
bool submitTask(wasm::CompileTask* task, wasm::CompileMode mode); bool submitTask(wasm::CompileTask* task, wasm::CompileMode mode);
bool submitTask(UniquePtr<jit::IonFreeTask> task, bool submitTask(UniquePtr<jit::IonFreeTask>&& task,
const AutoLockHelperThreadState& lock); const AutoLockHelperThreadState& lock);
bool submitTask(jit::IonCompileTask* task, bool submitTask(jit::IonCompileTask* task,
const AutoLockHelperThreadState& locked); const AutoLockHelperThreadState& locked);

View file

@ -258,19 +258,30 @@ bool GlobalHelperThreadState::submitTask(
return true; return true;
} }
bool js::StartOffThreadIonFree(jit::IonCompileTask* task, js::AutoStartIonFreeTask::~AutoStartIonFreeTask() {
const AutoLockHelperThreadState& lock) { if (tasks_.empty()) {
js::UniquePtr<jit::IonFreeTask> freeTask = return;
js::MakeUnique<jit::IonFreeTask>(task);
if (!freeTask) {
return false;
} }
return HelperThreadState().submitTask(std::move(freeTask), lock); auto freeTask = js::MakeUnique<jit::IonFreeTask>(std::move(tasks_));
if (!freeTask) {
// Free compilation data on the main thread instead.
MOZ_ASSERT(!tasks_.empty(), "shouldn't have moved tasks_ on OOM");
jit::FreeIonCompileTasks(tasks_);
return;
}
AutoLockHelperThreadState lock;
if (!HelperThreadState().submitTask(std::move(freeTask), lock)) {
// If submitTask OOMs, then freeTask hasn't been moved so we can still use
// its task list.
jit::FreeIonCompileTasks(freeTask->compileTasks());
}
} }
bool GlobalHelperThreadState::submitTask( bool GlobalHelperThreadState::submitTask(
UniquePtr<jit::IonFreeTask> task, const AutoLockHelperThreadState& locked) { UniquePtr<jit::IonFreeTask>&& task,
const AutoLockHelperThreadState& locked) {
MOZ_ASSERT(isInitialized(locked)); MOZ_ASSERT(isInitialized(locked));
if (!ionFreeList(locked).append(std::move(task))) { if (!ionFreeList(locked).append(std::move(task))) {
@ -343,64 +354,72 @@ static bool IonCompileTaskMatches(const CompilationSelector& selector,
return selector.match(TaskMatches{task}); return selector.match(TaskMatches{task});
} }
static void CancelOffThreadIonCompileLocked(const CompilationSelector& selector, void js::CancelOffThreadIonCompile(const CompilationSelector& selector) {
AutoLockHelperThreadState& lock) { if (!JitDataStructuresExist(selector)) {
if (jit::IsPortableBaselineInterpreterEnabled()) {
return; return;
} }
if (!HelperThreadState().isInitialized(lock)) { if (jit::IsPortableBaselineInterpreterEnabled()) {
return; return;
} }
MOZ_ASSERT(GetSelectorRuntime(selector)->jitRuntime() != nullptr); MOZ_ASSERT(GetSelectorRuntime(selector)->jitRuntime() != nullptr);
/* Cancel any pending entries for which processing hasn't started. */ AutoStartIonFreeTask freeTask;
GlobalHelperThreadState::IonCompileTaskVector& worklist =
HelperThreadState().ionWorklist(lock);
for (size_t i = 0; i < worklist.length(); i++) {
jit::IonCompileTask* task = worklist[i];
if (IonCompileTaskMatches(selector, task)) {
// Once finished, tasks are added to a Linked list which is
// allocated with the IonCompileTask class. The IonCompileTask is
// allocated in the LifoAlloc so we need the LifoAlloc to be mutable.
worklist[i]->alloc().lifoAlloc()->setReadWrite();
FinishOffThreadIonCompile(task, lock); {
HelperThreadState().remove(worklist, &i); AutoLockHelperThreadState lock;
if (!HelperThreadState().isInitialized(lock)) {
return;
} }
}
/* Wait for in progress entries to finish up. */ /* Cancel any pending entries for which processing hasn't started. */
bool cancelled; GlobalHelperThreadState::IonCompileTaskVector& worklist =
do { HelperThreadState().ionWorklist(lock);
cancelled = false; for (size_t i = 0; i < worklist.length(); i++) {
for (auto* helper : HelperThreadState().helperTasks(lock)) { jit::IonCompileTask* task = worklist[i];
if (!helper->is<jit::IonCompileTask>()) { if (IonCompileTaskMatches(selector, task)) {
continue; // Once finished, tasks are added to a Linked list which is
} // allocated with the IonCompileTask class. The IonCompileTask is
// allocated in the LifoAlloc so we need the LifoAlloc to be mutable.
worklist[i]->alloc().lifoAlloc()->setReadWrite();
jit::IonCompileTask* ionCompileTask = helper->as<jit::IonCompileTask>(); FinishOffThreadIonCompile(task, lock);
if (IonCompileTaskMatches(selector, ionCompileTask)) { HelperThreadState().remove(worklist, &i);
ionCompileTask->mirGen().cancel();
cancelled = true;
} }
} }
if (cancelled) {
HelperThreadState().wait(lock);
}
} while (cancelled);
/* Cancel code generation for any completed entries. */ /* Wait for in progress entries to finish up. */
GlobalHelperThreadState::IonCompileTaskVector& finished = bool cancelled;
HelperThreadState().ionFinishedList(lock); do {
for (size_t i = 0; i < finished.length(); i++) { cancelled = false;
jit::IonCompileTask* task = finished[i]; for (auto* helper : HelperThreadState().helperTasks(lock)) {
if (IonCompileTaskMatches(selector, task)) { if (!helper->is<jit::IonCompileTask>()) {
JSRuntime* rt = task->script()->runtimeFromAnyThread(); continue;
rt->jitRuntime()->numFinishedOffThreadTasksRef(lock)--; }
jit::FinishOffThreadTask(rt, task, lock);
HelperThreadState().remove(finished, &i); jit::IonCompileTask* ionCompileTask = helper->as<jit::IonCompileTask>();
if (IonCompileTaskMatches(selector, ionCompileTask)) {
ionCompileTask->mirGen().cancel();
cancelled = true;
}
}
if (cancelled) {
HelperThreadState().wait(lock);
}
} while (cancelled);
/* Cancel code generation for any completed entries. */
GlobalHelperThreadState::IonCompileTaskVector& finished =
HelperThreadState().ionFinishedList(lock);
for (size_t i = 0; i < finished.length(); i++) {
jit::IonCompileTask* task = finished[i];
if (IonCompileTaskMatches(selector, task)) {
JSRuntime* rt = task->script()->runtimeFromAnyThread();
rt->jitRuntime()->numFinishedOffThreadTasksRef(lock)--;
jit::FinishOffThreadTask(rt, freeTask, task);
HelperThreadState().remove(finished, &i);
}
} }
} }
@ -411,21 +430,12 @@ static void CancelOffThreadIonCompileLocked(const CompilationSelector& selector,
while (task) { while (task) {
jit::IonCompileTask* next = task->getNext(); jit::IonCompileTask* next = task->getNext();
if (IonCompileTaskMatches(selector, task)) { if (IonCompileTaskMatches(selector, task)) {
jit::FinishOffThreadTask(runtime, task, lock); jit::FinishOffThreadTask(runtime, freeTask, task);
} }
task = next; task = next;
} }
} }
void js::CancelOffThreadIonCompile(const CompilationSelector& selector) {
if (!JitDataStructuresExist(selector)) {
return;
}
AutoLockHelperThreadState lock;
CancelOffThreadIonCompileLocked(selector, lock);
}
#ifdef DEBUG #ifdef DEBUG
bool js::HasOffThreadIonCompile(Zone* zone) { bool js::HasOffThreadIonCompile(Zone* zone) {
if (jit::IsPortableBaselineInterpreterEnabled()) { if (jit::IsPortableBaselineInterpreterEnabled()) {
@ -817,7 +827,7 @@ void GlobalHelperThreadState::finish(AutoLockHelperThreadState& lock) {
while (!freeList.empty()) { while (!freeList.empty()) {
UniquePtr<jit::IonFreeTask> task = std::move(freeList.back()); UniquePtr<jit::IonFreeTask> task = std::move(freeList.back());
freeList.popBack(); freeList.popBack();
jit::FreeIonCompileTask(task->compileTask()); jit::FreeIonCompileTasks(task->compileTasks());
} }
} }
@ -998,8 +1008,9 @@ void GlobalHelperThreadState::addSizeOfIncludingThis(
htStats.ionCompileTask += task->sizeOfExcludingThis(mallocSizeOf); htStats.ionCompileTask += task->sizeOfExcludingThis(mallocSizeOf);
} }
for (const auto& task : ionFreeList_) { for (const auto& task : ionFreeList_) {
htStats.ionCompileTask += for (auto* compileTask : task->compileTasks()) {
task->compileTask()->sizeOfExcludingThis(mallocSizeOf); htStats.ionCompileTask += compileTask->sizeOfExcludingThis(mallocSizeOf);
}
} }
// Report wasm::CompileTasks on wait lists // Report wasm::CompileTasks on wait lists

View file

@ -13,8 +13,10 @@
#include "mozilla/Variant.h" #include "mozilla/Variant.h"
#include "js/AllocPolicy.h"
#include "js/shadow/Zone.h" #include "js/shadow/Zone.h"
#include "js/UniquePtr.h" #include "js/UniquePtr.h"
#include "js/Vector.h"
#include "threading/LockGuard.h" #include "threading/LockGuard.h"
#include "threading/Mutex.h" #include "threading/Mutex.h"
#include "wasm/WasmConstants.h" #include "wasm/WasmConstants.h"
@ -49,6 +51,7 @@ class GCRuntime;
namespace jit { namespace jit {
class IonCompileTask; class IonCompileTask;
class IonFreeTask; class IonFreeTask;
using IonFreeCompileTasks = Vector<IonCompileTask*, 8, SystemAllocPolicy>;
} // namespace jit } // namespace jit
namespace wasm { namespace wasm {
@ -140,15 +143,21 @@ bool StartOffThreadPromiseHelperTask(PromiseHelperTask* task);
bool StartOffThreadIonCompile(jit::IonCompileTask* task, bool StartOffThreadIonCompile(jit::IonCompileTask* task,
const AutoLockHelperThreadState& lock); const AutoLockHelperThreadState& lock);
/*
* Schedule deletion of Ion compilation data.
*/
bool StartOffThreadIonFree(jit::IonCompileTask* task,
const AutoLockHelperThreadState& lock);
void FinishOffThreadIonCompile(jit::IonCompileTask* task, void FinishOffThreadIonCompile(jit::IonCompileTask* task,
const AutoLockHelperThreadState& lock); const AutoLockHelperThreadState& lock);
// RAII class to submit an IonFreeTask for a list of Ion compilation tasks.
class MOZ_RAII AutoStartIonFreeTask {
jit::IonFreeCompileTasks tasks_;
public:
~AutoStartIonFreeTask();
[[nodiscard]] bool appendCompileTask(jit::IonCompileTask* task) {
return tasks_.append(task);
}
};
struct ZonesInState { struct ZonesInState {
JSRuntime* runtime; JSRuntime* runtime;
JS::shadow::Zone::GCState state; JS::shadow::Zone::GCState state;