mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 16:22:00 +00:00
Bug 1715976 - Add Delazification helper thread task. r=arai
This patch adds delazification on helper threads. This patch is quite convoluted as it includes all functions used to manipulate the DelazifyTask tasks. A DelazifyTask is an HelperThread task which uses Parse threads to parse and generate the bytecode of inner functions concurrently with the execution, and which are scheduled after the initial parse is completed. DelazifyTask, as opposed to other HelperThreadTask are tasks which are executed and re-scheduled as long as inner function remain to be delazified based on the strategy used by the DelazifyTask, implemented by a DelazifyStrategy. A DelazifyStrategy is a class which holds ScriptIndex-es to be looked at, and order them by priority or with a pre-defined order selected by the the CompileOption. As DelazifyTask-s are not attached to any object in the main thread, they are automatically discarded when the source is no longer registered in the StencilCache, or when the JSRuntime actively wait for the tasks to be collected before the process shutdown. Differential Revision: https://phabricator.services.mozilla.com/D136340
This commit is contained in:
parent
493b4192eb
commit
69eb6d5a3c
@ -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
|
||||
};
|
||||
|
||||
|
@ -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<js::UniquePtr<jit::IonFreeTask>, 0, SystemAllocPolicy>;
|
||||
typedef Vector<UniquePtr<ParseTask>, 0, SystemAllocPolicy> ParseTaskVector;
|
||||
using ParseTaskList = mozilla::LinkedList<ParseTask>;
|
||||
using DelazifyTaskList = mozilla::LinkedList<DelazifyTask>;
|
||||
using FreeDelazifyTaskVector =
|
||||
Vector<js::UniquePtr<FreeDelazifyTask>, 1, SystemAllocPolicy>;
|
||||
typedef Vector<UniquePtr<SourceCompressionTask>, 0, SystemAllocPolicy>
|
||||
SourceCompressionTaskVector;
|
||||
using GCParallelTaskList = mozilla::LinkedList<GCParallelTask>;
|
||||
@ -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<ParseTask> task,
|
||||
const AutoLockHelperThreadState& locked);
|
||||
void submitTask(DelazifyTask* task, const AutoLockHelperThreadState& locked);
|
||||
bool submitTask(UniquePtr<FreeDelazifyTask> task,
|
||||
const AutoLockHelperThreadState& locked);
|
||||
bool submitTask(PromiseHelperTask* task);
|
||||
bool submitTask(GCParallelTask* task,
|
||||
const AutoLockHelperThreadState& locked);
|
||||
@ -540,9 +575,126 @@ struct ParseTask : public mozilla::LinkedListElement<ParseTask>,
|
||||
|
||||
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<ScriptIndex, 0, SystemAllocPolicy> 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<DelazifyTask>,
|
||||
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<DelazifyStrategy> 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<frontend::ExtensibleCompilationStencil>&& 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.
|
||||
|
@ -13,6 +13,8 @@ namespace js {
|
||||
|
||||
class AutoLockHelperThreadState;
|
||||
struct ParseTask;
|
||||
struct DelazifyTask;
|
||||
struct FreeDelazifyTask;
|
||||
class SourceCompressionTask;
|
||||
|
||||
namespace jit {
|
||||
@ -41,6 +43,16 @@ struct MapTypeToThreadType<ParseTask> {
|
||||
static const ThreadType threadType = THREAD_TYPE_PARSE;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct MapTypeToThreadType<DelazifyTask> {
|
||||
static const ThreadType threadType = THREAD_TYPE_DELAZIFY;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct MapTypeToThreadType<FreeDelazifyTask> {
|
||||
static const ThreadType threadType = THREAD_TYPE_DELAZIFY_FREE;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct MapTypeToThreadType<SourceCompressionTask> {
|
||||
static const ThreadType threadType = THREAD_TYPE_COMPRESS;
|
||||
|
@ -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<TaggedScriptThingIndex>
|
||||
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#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<DelazifyTask> 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<DelazifyTask>(runtime));
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<ScriptSource> 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<frontend::ExtensibleCompilationStencil>(
|
||||
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 <typename Unit>
|
||||
struct CompileToStencilTask : public ParseTask {
|
||||
JS::SourceText<Unit> 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<frontend::ExtensibleCompilationStencil>&& 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<DepthFirstDelazification>();
|
||||
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<FreeDelazifyTask> freeTask(js_new<FreeDelazifyTask>(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<CompilationStencil> 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<DelazifyTask>() &&
|
||||
helper->as<DelazifyTask>()->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<DelazifyTask>(),
|
||||
!helper->as<DelazifyTask>()->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<FreeDelazifyTask>()) {
|
||||
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<ParseTask> 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<FreeDelazifyTask> task, const AutoLockHelperThreadState& locked) {
|
||||
if (!freeDelazifyTaskVector(locked).append(std::move(task))) {
|
||||
return false;
|
||||
}
|
||||
dispatch(locked);
|
||||
return true;
|
||||
}
|
||||
|
||||
static JS::OffThreadToken* StartOffThreadParseTask(
|
||||
JSContext* cx, UniquePtr<ParseTask> 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<FreeDelazifyTask> 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);
|
||||
|
@ -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.
|
||||
|
@ -269,6 +269,7 @@ void JSRuntime::destroyRuntime() {
|
||||
*/
|
||||
CancelOffThreadIonCompile(this);
|
||||
CancelOffThreadParses(this);
|
||||
CancelOffThreadDelazify(this);
|
||||
CancelOffThreadCompressions(this);
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user