diff --git a/js/public/Utility.h b/js/public/Utility.h index e649a64c502b..43a25185e6f8 100644 --- a/js/public/Utility.h +++ b/js/public/Utility.h @@ -66,6 +66,8 @@ enum ThreadType { THREAD_TYPE_ION_FREE, // 9 THREAD_TYPE_WASM_GENERATOR_TIER2, // 10 THREAD_TYPE_WORKER, // 11 + THREAD_TYPE_DELAZIFY, // 12 + THREAD_TYPE_DELAZIFY_FREE, // 13 THREAD_TYPE_MAX // Used to check shell function arguments }; diff --git a/js/src/vm/HelperThreadState.h b/js/src/vm/HelperThreadState.h index 43cabb4e5f79..ff97b0b25124 100644 --- a/js/src/vm/HelperThreadState.h +++ b/js/src/vm/HelperThreadState.h @@ -38,6 +38,8 @@ class AutoLockHelperThreadState; class AutoUnlockHelperThreadState; class CompileError; struct ParseTask; +struct DelazifyTask; +struct FreeDelazifyTask; struct PromiseHelperTask; class PromiseObject; @@ -108,6 +110,9 @@ class GlobalHelperThreadState { Vector, 0, SystemAllocPolicy>; typedef Vector, 0, SystemAllocPolicy> ParseTaskVector; using ParseTaskList = mozilla::LinkedList; + using DelazifyTaskList = mozilla::LinkedList; + using FreeDelazifyTaskVector = + Vector, 1, SystemAllocPolicy>; typedef Vector, 0, SystemAllocPolicy> SourceCompressionTaskVector; using GCParallelTaskList = mozilla::LinkedList; @@ -146,6 +151,19 @@ class GlobalHelperThreadState { ParseTaskVector parseWorklist_; ParseTaskList parseFinishedList_; + // Script worklist, which might still have function to delazify. + DelazifyTaskList delazifyWorklist_; + // Ideally an instance should not have a method to free it-self as, the method + // has a this pointer, which aliases the deleted instance, and that the method + // might have some of its fields aliased on the stack. + // + // Delazification task are complex and have a lot of fields. To reduce the + // risk of having aliased fields on the stack while deleting instances of a + // DelazifyTask, we have FreeDelazifyTask. While FreeDelazifyTask suffer from + // the same problem, the limited scope of their actions should mitigate the + // risk. + FreeDelazifyTaskVector freeDelazifyTaskVector_; + // Source compression worklist of tasks that we do not yet know can start. SourceCompressionTaskVector compressionPendingList_; @@ -299,6 +317,15 @@ class GlobalHelperThreadState { return parseFinishedList_; } + DelazifyTaskList& delazifyWorklist(const AutoLockHelperThreadState&) { + return delazifyWorklist_; + } + + FreeDelazifyTaskVector& freeDelazifyTaskVector( + const AutoLockHelperThreadState&) { + return freeDelazifyTaskVector_; + } + SourceCompressionTaskVector& compressionPendingList( const AutoLockHelperThreadState&) { return compressionPendingList_; @@ -339,6 +366,8 @@ class GlobalHelperThreadState { bool canStartIonCompileTask(const AutoLockHelperThreadState& lock); bool canStartIonFreeTask(const AutoLockHelperThreadState& lock); bool canStartParseTask(const AutoLockHelperThreadState& lock); + bool canStartFreeDelazifyTask(const AutoLockHelperThreadState& lock); + bool canStartDelazifyTask(const AutoLockHelperThreadState& lock); bool canStartCompressionTask(const AutoLockHelperThreadState& lock); bool canStartGCParallelTask(const AutoLockHelperThreadState& lock); @@ -359,6 +388,9 @@ class GlobalHelperThreadState { const AutoLockHelperThreadState& lock); HelperThreadTask* maybeGetIonFreeTask(const AutoLockHelperThreadState& lock); HelperThreadTask* maybeGetParseTask(const AutoLockHelperThreadState& lock); + HelperThreadTask* maybeGetFreeDelazifyTask( + const AutoLockHelperThreadState& lock); + HelperThreadTask* maybeGetDelazifyTask(const AutoLockHelperThreadState& lock); HelperThreadTask* maybeGetCompressionTask( const AutoLockHelperThreadState& lock); HelperThreadTask* maybeGetGCParallelTask( @@ -441,6 +473,9 @@ class GlobalHelperThreadState { const AutoLockHelperThreadState& locked); bool submitTask(JSRuntime* rt, UniquePtr task, const AutoLockHelperThreadState& locked); + void submitTask(DelazifyTask* task, const AutoLockHelperThreadState& locked); + bool submitTask(UniquePtr task, + const AutoLockHelperThreadState& locked); bool submitTask(PromiseHelperTask* task); bool submitTask(GCParallelTask* task, const AutoLockHelperThreadState& locked); @@ -540,9 +575,126 @@ struct ParseTask : public mozilla::LinkedListElement, void runHelperThreadTask(AutoLockHelperThreadState& locked) override; void runTask(AutoLockHelperThreadState& lock); + void scheduleDelazifyTask(AutoLockHelperThreadState& lock); ThreadType threadType() override { return ThreadType::THREAD_TYPE_PARSE; } }; +// Base class for implementing the various strategies to iterate over the +// functions to be delazified, or to decide when to stop doing any +// delazification. +// +// When created, the `add` function should be called with the top-level +// ScriptIndex. +struct DelazifyStrategy { + using ScriptIndex = frontend::ScriptIndex; + virtual ~DelazifyStrategy() = default; + + // Returns true if no more functions should be delazified. Note, this does not + // imply that every function got delazified. + virtual bool done() const = 0; + + // Return a function identifier which represent the next function to be + // delazified. If no more function should be delazified, then return 0. + virtual ScriptIndex next() = 0; + + // Empty the list of functions to be processed next. done() should return true + // after this call. + virtual void clear() = 0; + + // Add the inner functions of a delazified function. This function should only + // be called with a function which has some bytecode associated with it, and + // register functions which parent are already delazified. + // + // This function is called with the script index of: + // - top-level script, when starting the off-thread delazification. + // - functions added by `add` and delazified by `DelazifyTask`. + [[nodiscard]] virtual bool add(JSContext* cx, + const frontend::CompilationStencil& stencil, + ScriptIndex index) = 0; +}; + +// Delazify all functions using a Depth First traversal of the function-tree +// ordered, where each functions is visited in source-order. +// +// When `add` is called with the top-level ScriptIndex. This will push all inner +// functions to a stack such that they are popped in source order. Each +// function, once delazified, would be used to schedule their inner functions +// the same way. +// +// Hypothesis: This strategy parses all functions in source order, with the +// expectation that calls will follow the same order, and that helper thread +// would always be ahead of the execution. +struct DepthFirstDelazification final : public DelazifyStrategy { + Vector stack; + + bool done() const override { return stack.empty(); } + ScriptIndex next() override { return stack.popCopy(); } + void clear() override { return stack.clear(); } + [[nodiscard]] bool add(JSContext* cx, + const frontend::CompilationStencil& stencil, + ScriptIndex index) override; +}; + +// Eagerly delazify functions, and send the result back to the runtime which +// requested the stencil to be parsed, by filling the stencil cache. +// +// This task is scheduled multiple times, each time it is scheduled, it +// delazifies a single function. Once the function is delazified, it schedules +// the inner functions of the delazified function for delazification using the +// DelazifyStrategy. The DelazifyStrategy is responsible for ordering and +// filtering functions to be delazified. +// +// When no more function have to be delazified, a FreeDelazifyTask is scheduled +// to remove the memory held by the DelazifyTask. +struct DelazifyTask : public mozilla::LinkedListElement, + public HelperThreadTask { + // HelperThreads are shared between all runtimes in the process so explicitly + // track which one we are associated with. + JSRuntime* runtime = nullptr; + + // Queue of functions to be processed while delazifying. + UniquePtr strategy; + + // Every delazified function is merged back to provide context for delazifying + // even more functions. + frontend::CompilationStencilMerger merger; + + // Record any errors happening while parsing or generating bytecode. + OffThreadFrontendErrors errors_; + + explicit DelazifyTask(JSRuntime* runtime); + + [[nodiscard]] bool init( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + UniquePtr&& initial); + + bool runtimeMatches(JSRuntime* rt) { return runtime == rt; } + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } + + void runHelperThreadTask(AutoLockHelperThreadState& locked) override; + [[nodiscard]] bool runTask(JSContext* cx); + ThreadType threadType() override { return ThreadType::THREAD_TYPE_DELAZIFY; } +}; + +// The FreeDelazifyTask exists as this is a bad practice to `js_delete(this)`, +// as fields might be aliased across the destructor, such as with RAII guards. +// The FreeDelazifyTask limits the risk of adding these kind of issues by +// limiting the number of fields to the DelazifyTask pointer, before deleting +// it-self. +struct FreeDelazifyTask : public HelperThreadTask { + DelazifyTask* task; + + explicit FreeDelazifyTask(DelazifyTask* t) : task(t) {} + void runHelperThreadTask(AutoLockHelperThreadState& locked) override; + ThreadType threadType() override { + return ThreadType::THREAD_TYPE_DELAZIFY_FREE; + } +}; + // It is not desirable to eagerly compress: if lazy functions that are tied to // the ScriptSource were to be executed relatively soon after parsing, they // would need to block on decompression, which hurts responsiveness. diff --git a/js/src/vm/HelperThreadTask.h b/js/src/vm/HelperThreadTask.h index 175e16561036..c867d4461546 100644 --- a/js/src/vm/HelperThreadTask.h +++ b/js/src/vm/HelperThreadTask.h @@ -13,6 +13,8 @@ namespace js { class AutoLockHelperThreadState; struct ParseTask; +struct DelazifyTask; +struct FreeDelazifyTask; class SourceCompressionTask; namespace jit { @@ -41,6 +43,16 @@ struct MapTypeToThreadType { static const ThreadType threadType = THREAD_TYPE_PARSE; }; +template <> +struct MapTypeToThreadType { + static const ThreadType threadType = THREAD_TYPE_DELAZIFY; +}; + +template <> +struct MapTypeToThreadType { + static const ThreadType threadType = THREAD_TYPE_DELAZIFY_FREE; +}; + template <> struct MapTypeToThreadType { static const ThreadType threadType = THREAD_TYPE_COMPRESS; diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 6ef6124cdec9..56f74bbc78bd 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -8,14 +8,16 @@ #include "mozilla/DebugOnly.h" #include "mozilla/Maybe.h" +#include "mozilla/ReverseIterator.h" // mozilla::Reversed(...) #include "mozilla/ScopeExit.h" +#include "mozilla/Span.h" // mozilla::Span #include "mozilla/Utf8.h" // mozilla::Utf8Unit #include -#include "frontend/BytecodeCompilation.h" // frontend::CompileGlobalScriptToStencil -#include "frontend/BytecodeCompiler.h" // frontend::ParseModuleToStencil -#include "frontend/CompilationStencil.h" // frontend::{CompilationStencil, CompilationInput, CompilationGCOutput} +#include "frontend/BytecodeCompilation.h" // frontend::{CompileGlobalScriptToExtensibleStencil, FireOnNewScript} +#include "frontend/BytecodeCompiler.h" // frontend::ParseModuleToExtensibleStencil +#include "frontend/CompilationStencil.h" // frontend::{CompilationStencil, ExtensibleCompilationStencil, CompilationInput, BorrowingCompilationStencil, ScriptStencilRef} #include "jit/IonCompileTask.h" #include "jit/JitRuntime.h" #include "js/CompileOptions.h" // JS::CompileOptions, JS::DecodeOptions, JS::ReadOnlyCompileOptions @@ -598,6 +600,11 @@ size_t ParseTask::sizeOfExcludingThis( void ParseTask::runHelperThreadTask(AutoLockHelperThreadState& locked) { runTask(locked); + // Schedule DelazifyTask if needed. NOTE: This should be done before adding + // this task to the finished list, as we temporarily release the lock to make + // a few large allocations. + scheduleDelazifyTask(locked); + // The callback is invoked while we are still off thread. callback(this, callbackData); @@ -623,6 +630,66 @@ void ParseTask::runTask(AutoLockHelperThreadState& lock) { cx->frontendCollectionPool().purge(); } +void ParseTask::scheduleDelazifyTask(AutoLockHelperThreadState& lock) { + if (!stencil_) { + return; + } + + // Skip delazify tasks if we parese everything on-demand or ahead. + auto strategy = options.eagerDelazificationStrategy(); + if (strategy == JS::DelazificationOption::OnDemandOnly || + strategy == JS::DelazificationOption::ParseEverythingEagerly) { + return; + } + + UniquePtr task; + { + AutoSetHelperThreadContext usesContext(lock); + AutoUnlockHelperThreadState unlock(lock); + JSContext* cx = TlsContext.get(); + AutoSetContextRuntime ascr(runtime); + + // DelazifyTask are capturing errors. This is created here to capture errors + // as-if they were part of the to-be constructed DelazifyTask. This is also + // the reason why we move this structure to the DelazifyTask once created. + // + // In case of early failure, no errors are reported, as a DelazifyTask is an + // optimization and the VM should remain working even without this + // optimization in place. + OffThreadFrontendErrors errors; + AutoSetContextOffThreadFrontendErrors recordErrors(&errors); + + task.reset(js_new(runtime)); + if (!task) { + return; + } + + RefPtr source(stencil_->source); + StencilCache& cache = runtime->caches().delazificationCache; + if (!cache.startCaching(std::move(source))) { + return; + } + + // Clone the extensible stencil to be used for eager delazification. + auto initial = cx->make_unique( + cx, options, stencil_->source); + if (!initial->cloneFrom(cx, *stencil_)) { + // In case of errors, skip this and delazify on-demand. + return; + } + + if (!task->init(cx, options, std::move(initial))) { + // In case of errors, skip this and delazify on-demand. + return; + } + + task->errors_ = std::move(errors); + } + + // Schedule delazification task. + HelperThreadState().submitTask(task.release(), lock); +} + template struct CompileToStencilTask : public ParseTask { JS::SourceText data; @@ -793,6 +860,199 @@ void MultiStencilsDecodeTask::parse(JSContext* cx) { } } +bool DepthFirstDelazification::add(JSContext* cx, + const frontend::CompilationStencil& stencil, + ScriptIndex index) { + using namespace js::frontend; + ScriptStencilRef scriptRef{stencil, index}; + + // Only functions with bytecode are allowed to be added. + MOZ_ASSERT(!scriptRef.scriptData().isGhost()); + MOZ_ASSERT(scriptRef.scriptData().hasSharedData()); + + // Lookup the gc-things range which are referenced by this script. + size_t offset = scriptRef.scriptData().gcThingsOffset.index; + size_t length = scriptRef.scriptData().gcThingsLength; + auto gcThingData = stencil.gcThingData.Subspan(offset, length); + + // Iterate over gc-things of the script and queue inner functions. + for (TaggedScriptThingIndex index : mozilla::Reversed(gcThingData)) { + if (!index.isFunction()) { + continue; + } + + ScriptIndex innerScriptIndex = index.toFunction(); + ScriptStencilRef innerScriptRef{stencil, innerScriptIndex}; + if (innerScriptRef.scriptData().isGhost()) { + continue; + } + if (innerScriptRef.scriptData().hasSharedData()) { + // The top-level parse decided to eagerly parse this function, thus we + // should visit its inner function the same way. + if (!add(cx, stencil, innerScriptIndex)) { + return false; + } + continue; + } + + if (!stack.append(innerScriptIndex)) { + ReportOutOfMemory(cx); + return false; + } + } + + return true; +} + +DelazifyTask::DelazifyTask(JSRuntime* runtime) + : runtime(runtime), merger(), errors_() { + AutoLockScriptData alsd(runtime); + runtime->addParseTaskRef(); +} + +bool DelazifyTask::init( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + UniquePtr&& initial) { + using namespace js::frontend; + if (!merger.setInitial(cx, std::move(initial))) { + return false; + } + + switch (options.eagerDelazificationStrategy()) { + case JS::DelazificationOption::OnDemandOnly: + // OnDemandOnly will parse function as they are require to continue the + // execution on the main thread. + MOZ_CRASH("OnDemandOnly should not create a DelazifyTask."); + break; + case JS::DelazificationOption::ConcurrentDepthFirst: + // ConcurrentDepthFirst visit all functions to be delazified, visiting the + // inner functions before the siblings functions. + strategy = cx->make_unique(); + break; + case JS::DelazificationOption::ConcurrentBreathFirst: + // ConcurrentDepthFirst visit all functions to be delazified, visiting the + // siblings functions before the inner functions. + MOZ_CRASH("Strategy is not yet implemented"); + break; + case JS::DelazificationOption::ConcurrentMostFrequentNameFirst: + // ConcurrentMostFrequentNameFirst uses the frequency of names to + // determine the order in which functions should be delazified. Unamed + // functions are delazified first. + MOZ_CRASH("Strategy is not yet implemented"); + break; + case JS::DelazificationOption::ParseEverythingEagerly: + // ParseEverythingEagerly parse all functions eagerly, thus leaving no + // functions to be parsed on demand. + MOZ_CRASH("ParseEverythingEagerly should not create a DelazifyTask"); + break; + } + + // Queue functions from the top-level to be delazify. + BorrowingCompilationStencil borrow(merger.getResult()); + ScriptIndex topLevel{0}; + return strategy->add(cx, borrow, topLevel); +} + +size_t DelazifyTask::sizeOfExcludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t mergerSize = merger.getResult().sizeOfIncludingThis(mallocSizeOf); + return mergerSize; +} + +void DelazifyTask::runHelperThreadTask(AutoLockHelperThreadState& lock) { + { + AutoSetHelperThreadContext usesContext(lock); + AutoUnlockHelperThreadState unlock(lock); + JSContext* cx = TlsContext.get(); + if (!runTask(cx)) { + // NOTE: We do not report errors beyond this scope, as there is no where + // to report these errors to. In the mean time, prevent the eager + // delazification from running after any kind of errors. + strategy->clear(); + } + MOZ_ASSERT(cx->tempLifoAlloc().isEmpty()); + cx->tempLifoAlloc().freeAll(); + cx->frontendCollectionPool().purge(); + } + + // If we should continue to delazify even more functions, then re-add this + // task to the vector of delazification tasks. + if (!strategy->done()) { + HelperThreadState().submitTask(this, lock); + } else { + UniquePtr freeTask(js_new(this)); + if (freeTask) { + HelperThreadState().submitTask(std::move(freeTask), lock); + } + } +} + +bool DelazifyTask::runTask(JSContext* cx) { + AutoSetContextRuntime ascr(runtime); + AutoSetContextOffThreadFrontendErrors recordErrors(&this->errors_); + gc::AutoSuppressNurseryCellAlloc noNurseryAlloc(cx); + + using namespace js::frontend; + RefPtr innerStencil; + ScriptIndex scriptIndex = strategy->next(); + { + BorrowingCompilationStencil borrow(merger.getResult()); + + // Take the next inner function to be delazified. + ScriptStencilRef scriptRef{borrow, scriptIndex}; + MOZ_ASSERT(!scriptRef.scriptData().isGhost()); + MOZ_ASSERT(!scriptRef.scriptData().hasSharedData()); + + // Parse and generate bytecode for the inner function. + innerStencil = DelazifyCanonicalScriptedFunction(cx, borrow, scriptIndex); + if (!innerStencil) { + return false; + } + + // Add the generated stencil to the cache, to be consumed by the main + // thread. + StencilCache& cache = runtime->caches().delazificationCache; + StencilContext key(borrow.source, scriptRef.scriptExtra().extent); + if (auto guard = cache.isSourceCached(borrow.source)) { + if (!cache.putNew(guard, key, innerStencil.get())) { + ReportOutOfMemory(cx); + return false; + } + } else { + // Stencils for this source are not longer accepted in the cache, thus + // there is no reason to keep our eager delazification going. + strategy->clear(); + return true; + } + } + + // We are merging the delazification now, while this could be post-poned until + // we have to look at inner functions, this is simpler to do it now than + // querying the cache for every enclosing script. + if (!merger.addDelazification(cx, *innerStencil)) { + return false; + } + + { + BorrowingCompilationStencil borrow(merger.getResult()); + if (!strategy->add(cx, borrow, scriptIndex)) { + return false; + } + } + + return true; +} + +void FreeDelazifyTask::runHelperThreadTask(AutoLockHelperThreadState& locked) { + { + AutoUnlockHelperThreadState unlock(locked); + js_delete(task); + task = nullptr; + } + + js_delete(this); +} + static void WaitForOffThreadParses(JSRuntime* rt, AutoLockHelperThreadState& lock) { if (!HelperThreadState().isInitialized(lock)) { @@ -872,6 +1132,103 @@ void js::CancelOffThreadParses(JSRuntime* rt) { #endif } +static void CancelPendingDelazifyTask(JSRuntime* rt, + AutoLockHelperThreadState& lock) { + auto& delazifyList = HelperThreadState().delazifyWorklist(lock); + + auto end = delazifyList.end(); + for (auto iter = delazifyList.begin(); iter != end;) { + DelazifyTask* task = *iter; + ++iter; + if (task->runtimeMatches(rt)) { + task->removeFrom(delazifyList); + js_delete(task); + } + } +} + +static void WaitUntilCancelledDelazifyTasks(JSRuntime* rt, + AutoLockHelperThreadState& lock) { + if (!HelperThreadState().isInitialized(lock)) { + return; + } + + while (true) { + CancelPendingDelazifyTask(rt, lock); + + // If running tasks are delazifying any functions, then we have to wait + // until they complete to remove them from the pending list. DelazifyTask + // are inserting themself back to be processed once more after delazifying a + // function. + bool inProgress = false; + for (auto* helper : HelperThreadState().helperTasks(lock)) { + if (helper->is() && + helper->as()->runtimeMatches(rt)) { + inProgress = true; + break; + } + } + if (!inProgress) { + break; + } + + HelperThreadState().wait(lock); + } + +#ifdef DEBUG + for (DelazifyTask* task : HelperThreadState().delazifyWorklist(lock)) { + MOZ_ASSERT(!task->runtimeMatches(rt)); + } + for (auto* helper : HelperThreadState().helperTasks(lock)) { + MOZ_ASSERT_IF(helper->is(), + !helper->as()->runtimeMatches(rt)); + } +#endif +} + +static void WaitUntilEmptyFreeDelazifyTaskVector( + AutoLockHelperThreadState& lock) { + if (!HelperThreadState().isInitialized(lock)) { + return; + } + + while (true) { + bool inProgress = false; + auto& freeList = HelperThreadState().freeDelazifyTaskVector(lock); + if (!freeList.empty()) { + inProgress = true; + } + + // If running tasks are delazifying any functions, then we have to wait + // until they complete to remove them from the pending list. DelazifyTask + // are inserting themself back to be processed once more after delazifying a + // function. + for (auto* helper : HelperThreadState().helperTasks(lock)) { + if (helper->is()) { + inProgress = true; + break; + } + } + if (!inProgress) { + break; + } + + HelperThreadState().wait(lock); + } +} + +void js::CancelOffThreadDelazify(JSRuntime* runtime) { + AutoLockHelperThreadState lock; + + // Cancel all Delazify tasks from the given runtime, and wait if tasks are + // from the given runtime are being executed. + WaitUntilCancelledDelazifyTasks(runtime, lock); + + // Empty the free list of delazify task, in case one of the delazify task + // ended and therefore did not returned to the pending list of delazify tasks. + WaitUntilEmptyFreeDelazifyTaskVector(lock); +} + static bool QueueOffThreadParseTask(JSContext* cx, UniquePtr task) { AutoLockHelperThreadState lock; @@ -897,6 +1254,21 @@ bool GlobalHelperThreadState::submitTask( return true; } +void GlobalHelperThreadState::submitTask( + DelazifyTask* task, const AutoLockHelperThreadState& locked) { + delazifyWorklist(locked).insertBack(task); + dispatch(locked); +} + +bool GlobalHelperThreadState::submitTask( + UniquePtr task, const AutoLockHelperThreadState& locked) { + if (!freeDelazifyTaskVector(locked).append(std::move(task))) { + return false; + } + dispatch(locked); + return true; +} + static JS::OffThreadToken* StartOffThreadParseTask( JSContext* cx, UniquePtr task, const ReadOnlyCompileOptions& options) { @@ -1649,6 +2021,44 @@ bool GlobalHelperThreadState::canStartParseTask( /*isMaster=*/true, lock); } +HelperThreadTask* GlobalHelperThreadState::maybeGetFreeDelazifyTask( + const AutoLockHelperThreadState& lock) { + auto& freeList = freeDelazifyTaskVector(lock); + if (!freeList.empty()) { + UniquePtr task = std::move(freeList.back()); + freeList.popBack(); + return task.release(); + } + return nullptr; +} + +bool GlobalHelperThreadState::canStartFreeDelazifyTask( + const AutoLockHelperThreadState& lock) { + return !freeDelazifyTaskVector(lock).empty() && + checkTaskThreadLimit(THREAD_TYPE_DELAZIFY_FREE, maxParseThreads(), + /*isMaster=*/true, lock); +} + +HelperThreadTask* GlobalHelperThreadState::maybeGetDelazifyTask( + const AutoLockHelperThreadState& lock) { + // NOTE: We want to span all cores availables with delazification tasks, in + // order to parse a maximum number of functions ahead of their executions. + // Thus, as opposed to parse task which have a higher priority, we are not + // exclusively executing these task on parse threads. + auto& worklist = delazifyWorklist(lock); + if (worklist.isEmpty()) { + return nullptr; + } + return worklist.popFirst(); +} + +bool GlobalHelperThreadState::canStartDelazifyTask( + const AutoLockHelperThreadState& lock) { + return !delazifyWorklist(lock).isEmpty() && + checkTaskThreadLimit(THREAD_TYPE_DELAZIFY, maxParseThreads(), + /*isMaster=*/true, lock); +} + HelperThreadTask* GlobalHelperThreadState::maybeGetCompressionTask( const AutoLockHelperThreadState& lock) { if (!canStartCompressionTask(lock)) { @@ -2175,6 +2585,8 @@ const GlobalHelperThreadState::Selector GlobalHelperThreadState::selectors[] = { &GlobalHelperThreadState::maybeGetWasmTier1CompileTask, &GlobalHelperThreadState::maybeGetPromiseHelperTask, &GlobalHelperThreadState::maybeGetParseTask, + &GlobalHelperThreadState::maybeGetFreeDelazifyTask, + &GlobalHelperThreadState::maybeGetDelazifyTask, &GlobalHelperThreadState::maybeGetCompressionTask, &GlobalHelperThreadState::maybeGetLowPrioIonCompileTask, &GlobalHelperThreadState::maybeGetIonFreeTask, @@ -2186,6 +2598,7 @@ bool GlobalHelperThreadState::canStartTasks( return canStartGCParallelTask(lock) || canStartIonCompileTask(lock) || canStartWasmTier1CompileTask(lock) || canStartPromiseHelperTask(lock) || canStartParseTask(lock) || + canStartFreeDelazifyTask(lock) || canStartDelazifyTask(lock) || canStartCompressionTask(lock) || canStartIonFreeTask(lock) || canStartWasmTier2CompileTask(lock) || canStartWasmTier2GeneratorTask(lock); diff --git a/js/src/vm/HelperThreads.h b/js/src/vm/HelperThreads.h index 41c8948c0b98..cf8fb87aa7c2 100644 --- a/js/src/vm/HelperThreads.h +++ b/js/src/vm/HelperThreads.h @@ -198,6 +198,12 @@ bool CurrentThreadIsParseThread(); */ void CancelOffThreadParses(JSRuntime* runtime); +/* + * Cancel all scheduled or in progress eager delazification phases for a + * runtime. + */ +void CancelOffThreadDelazify(JSRuntime* runtime); + /* * Start a parse/emit cycle for a stream of source. The characters must stay * alive until the compilation finishes. diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 78044db90a4f..99fabdcc35b5 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -269,6 +269,7 @@ void JSRuntime::destroyRuntime() { */ CancelOffThreadIonCompile(this); CancelOffThreadParses(this); + CancelOffThreadDelazify(this); CancelOffThreadCompressions(this); /*