Bug 1845638 - Part 1: Use JS::FrontendContext APIs and TaskController in compilation and decode in ScriptLoader. r=smaug,bthrall

Differential Revision: https://phabricator.services.mozilla.com/D184896
This commit is contained in:
Tooru Fujisawa 2023-08-29 12:07:18 +00:00
parent 3aed59e248
commit 70c40a1658
9 changed files with 400 additions and 130 deletions

View File

@ -21,7 +21,6 @@
#include "js/Conversions.h"
#include "js/experimental/JSStencil.h"
#include "js/HeapAPI.h"
#include "js/OffThreadScriptCompilation.h"
#include "js/ProfilingCategory.h"
#include "js/Promise.h"
#include "js/SourceText.h"
@ -30,6 +29,7 @@
#include "js/Wrapper.h"
#include "jsapi.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/dom/ScriptLoadContext.h"
#include "mozilla/Likely.h"
#include "nsContentUtils.h"
#include "nsTPromiseFlatString.h"
@ -87,8 +87,7 @@ JSExecutionContext::JSExecutionContext(
}
}
nsresult JSExecutionContext::JoinOffThread(
JS::OffThreadToken** aOffThreadToken) {
nsresult JSExecutionContext::JoinOffThread(ScriptLoadContext* aContext) {
if (mSkip) {
return mRv;
}
@ -96,9 +95,7 @@ nsresult JSExecutionContext::JoinOffThread(
MOZ_ASSERT(!mWantsReturnValue);
JS::InstantiationStorage storage;
RefPtr<JS::Stencil> stencil =
JS::FinishOffThreadStencil(mCx, *aOffThreadToken, &storage);
*aOffThreadToken = nullptr; // Mark the token as having been finished.
RefPtr<JS::Stencil> stencil = aContext->StealOffThreadResult(mCx, &storage);
if (!stencil) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);

View File

@ -8,7 +8,6 @@
#define DOM_BASE_JSEXECUTIONCONTEXT_H_
#include "js/GCVector.h"
#include "js/OffThreadScriptCompilation.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
#include "js/experimental/JSStencil.h"
@ -30,6 +29,8 @@ union Utf8Unit;
namespace dom {
class ScriptLoadContext;
class MOZ_STACK_CLASS JSExecutionContext final {
// Register stack annotations for the Gecko profiler.
mozilla::AutoProfilerLabel mAutoProfilerLabel;
@ -128,9 +129,9 @@ class MOZ_STACK_CLASS JSExecutionContext final {
}
// After getting a notification that an off-thread compile/decode finished,
// this function will take the result of the parser and move it to the main
// thread.
[[nodiscard]] nsresult JoinOffThread(JS::OffThreadToken** aOffThreadToken);
// this function will take the result of the off-thread operation and move it
// to the main thread.
[[nodiscard]] nsresult JoinOffThread(ScriptLoadContext* aContext);
// Compile a script contained in a SourceText.
nsresult Compile(JS::SourceText<char16_t>& aSrcBuf);

View File

@ -13,7 +13,6 @@
#include "js/experimental/JSStencil.h" // JS::Stencil, JS::CompileModuleScriptToStencil, JS::InstantiateModuleStencil
#include "js/MemoryFunctions.h"
#include "js/Modules.h" // JS::FinishDynamicModuleImport, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{DynamicImport,Metadata}Hook
#include "js/OffThreadScriptCompilation.h"
#include "js/PropertyAndElement.h" // JS_DefineProperty
#include "js/Realm.h"
#include "js/SourceText.h"
@ -150,11 +149,8 @@ nsresult ModuleLoader::CompileFetchedModule(
ModuleLoadRequest* aRequest, JS::MutableHandle<JSObject*> aModuleOut) {
if (aRequest->GetScriptLoadContext()->mWasCompiledOMT) {
JS::InstantiationStorage storage;
RefPtr<JS::Stencil> stencil = JS::FinishOffThreadStencil(
aCx, aRequest->GetScriptLoadContext()->mOffThreadToken, &storage);
aRequest->GetScriptLoadContext()->mOffThreadToken = nullptr;
RefPtr<JS::Stencil> stencil =
aRequest->GetScriptLoadContext()->StealOffThreadResult(aCx, &storage);
if (!stencil) {
return NS_ERROR_FAILURE;
}

View File

@ -12,7 +12,6 @@
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "js/OffThreadScriptCompilation.h"
#include "js/SourceText.h"
#include "js/loader/LoadContextBase.h"
#include "js/loader/ModuleLoadRequest.h"
@ -37,7 +36,7 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptLoadContext)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ScriptLoadContext,
JS::loader::LoadContextBase)
MOZ_ASSERT(!tmp->mOffThreadToken);
MOZ_ASSERT(!tmp->mCompileOrDecodeTask);
MOZ_ASSERT(!tmp->mRunnable);
tmp->MaybeUnblockOnload();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -62,7 +61,6 @@ ScriptLoadContext::ScriptLoadContext()
mInCompilingList(false),
mIsTracking(false),
mWasCompiledOMT(false),
mOffThreadToken(nullptr),
mRunnable(nullptr),
mLineNo(1),
mColumnNo(0),
@ -72,8 +70,8 @@ ScriptLoadContext::ScriptLoadContext()
ScriptLoadContext::~ScriptLoadContext() {
MOZ_ASSERT(NS_IsMainThread());
// Off-thread parsing must have completed by this point.
MOZ_DIAGNOSTIC_ASSERT(!mOffThreadToken && !mRunnable);
// Off-thread parsing must have completed or cancelled by this point.
MOZ_DIAGNOSTIC_ASSERT(!mCompileOrDecodeTask && !mRunnable);
mRequest = nullptr;
@ -96,15 +94,13 @@ void ScriptLoadContext::MaybeUnblockOnload() {
void ScriptLoadContext::MaybeCancelOffThreadScript() {
MOZ_ASSERT(NS_IsMainThread());
if (!mOffThreadToken) {
if (!mCompileOrDecodeTask) {
return;
}
// Cancel parse if it hasn't been started yet or wait for it to finish and
// clean up finished parse data.
JSContext* cx = danger::GetJSContext();
JS::CancelOffThreadToken(cx, mOffThreadToken);
mOffThreadToken = nullptr;
// Cancel the task if it hasn't been started yet or wait for it to finish.
mCompileOrDecodeTask->Cancel();
mCompileOrDecodeTask = nullptr;
// Clear the pointer to the runnable. It may still run later if we didn't
// cancel in time. In this case the runnable is held live by the reference
@ -214,4 +210,12 @@ void ScriptLoadContext::GetProfilerLabel(nsACString& aOutString) {
}
}
already_AddRefed<JS::Stencil> ScriptLoadContext::StealOffThreadResult(
JSContext* aCx, JS::InstantiationStorage* aInstantiationStorage) {
RefPtr<CompileOrDecodeTask> compileOrDecodeTask =
mCompileOrDecodeTask.forget();
return compileOrDecodeTask->StealResult(aCx, aInstantiationStorage);
}
} // namespace mozilla::dom

View File

@ -8,11 +8,15 @@
#define mozilla_dom_ScriptLoadContext_h
#include "js/AllocPolicy.h"
#include "js/CompileOptions.h" // JS::OwningCompileOptions
#include "js/experimental/JSStencil.h" // JS::FrontendContext, JS::Stencil, JS::InstantiationStorage
#include "js/RootingAPI.h"
#include "js/SourceText.h"
#include "js/Transcoding.h" // JS::TranscodeResult
#include "js/TypeDecls.h"
#include "js/loader/LoadContextBase.h"
#include "js/loader/ScriptKind.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Atomics.h"
#include "mozilla/Assertions.h"
#include "mozilla/CORSMode.h"
@ -20,9 +24,12 @@
#include "mozilla/LinkedList.h"
#include "mozilla/Maybe.h"
#include "mozilla/MaybeOneOf.h"
#include "mozilla/Mutex.h"
#include "mozilla/PreloaderBase.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "mozilla/TaskController.h" // mozilla::Task
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "mozilla/Variant.h"
#include "mozilla/Vector.h"
#include "nsCOMPtr.h"
@ -30,10 +37,7 @@
#include "nsIScriptElement.h"
class nsICacheInfoChannel;
namespace JS {
class OffThreadToken;
} // namespace JS
struct JSContext;
namespace mozilla::dom {
@ -75,6 +79,71 @@ class Element;
*
*/
class OffThreadCompilationCompleteRunnable;
// Base class for the off-thread compile or off-thread decode tasks.
class CompileOrDecodeTask : public mozilla::Task {
protected:
explicit CompileOrDecodeTask(
OffThreadCompilationCompleteRunnable* aCompleteRunnable);
virtual ~CompileOrDecodeTask();
nsresult InitFrontendContext();
void DidRunTask(const MutexAutoLock& aProofOfLock,
RefPtr<JS::Stencil>&& aStencil);
bool IsCancelled(const MutexAutoLock& aProofOfLock) const {
return !mCompleteRunnable;
}
public:
// Returns the result of the compilation or decode if it was successful.
// Returns nullptr otherwise, and sets pending exception on JSContext.
//
// aInstantiationStorage receives the storage allocated off main thread
// on successful case.
already_AddRefed<JS::Stencil> StealResult(
JSContext* aCx, JS::InstantiationStorage* aInstantiationStorage);
// Cancel the task.
// If the task is already running, this waits for the task to finish.
void Cancel();
protected:
static constexpr size_t kDefaultStackQuota = 128 * sizeof(size_t) * 1024;
// This mutex is locked during running the task or cancelling task.
mozilla::Mutex mMutex;
// The result of decode task, to distinguish throwing case and decode error.
JS::TranscodeResult mResult = JS::TranscodeResult::Ok;
// An option used to compile the code, or the equivalent for decode.
// This holds the filename pointed by errors reported to JS::FrontendContext.
JS::OwningCompileOptions mOptions;
// Owning-pointer for the context associated with the script compilation.
//
// The context is allocated on main thread in InitFrontendContext method,
// and is freed on any thread in the destructor.
JS::FrontendContext* mFrontendContext = nullptr;
// The pointed OffThreadCompilationCompleteRunnable is kept alive by
// ScriptLoadContext::mRunnable.
//
// This shouldn't be RefPtr, given this task can be freed off main thread.
//
// If this task is cancelled before running, this field is cleared.
OffThreadCompilationCompleteRunnable* mCompleteRunnable;
private:
// The result of the compilation or decode.
RefPtr<JS::Stencil> mStencil;
JS::InstantiationStorage mInstantiationStorage;
};
class ScriptLoadContext : public JS::loader::LoadContextBase,
public PreloaderBase {
protected:
@ -95,10 +164,6 @@ class ScriptLoadContext : public JS::loader::LoadContextBase,
bool CompileStarted() const;
JS::OffThreadToken** OffThreadTokenPtr() {
return mOffThreadToken ? &mOffThreadToken : nullptr;
}
bool IsTracking() const { return mIsTracking; }
void SetIsTracking() {
MOZ_ASSERT(!mIsTracking);
@ -154,6 +219,11 @@ class ScriptLoadContext : public JS::loader::LoadContextBase,
void MaybeCancelOffThreadScript();
// Finish the off-main-thread compilation and return the result, or
// convert the compilation error to runtime error.
already_AddRefed<JS::Stencil> StealOffThreadResult(
JSContext* aCx, JS::InstantiationStorage* aInstantiationStorage);
ScriptMode mScriptMode; // Whether this is a blocking, defer or async script.
bool mScriptFromHead; // Synchronous head script block loading of other non
// js/css content.
@ -170,9 +240,12 @@ class ScriptLoadContext : public JS::loader::LoadContextBase,
bool mWasCompiledOMT; // True if the script has been compiled off main
// thread.
// Off-thread parsing token. Set at the start of off-thread parsing and
// cleared when the result of the parse is used.
JS::OffThreadToken* mOffThreadToken;
// Task that performs off-thread compilation or off-thread decode.
// This field is used to take the result of the task, or cancel the task.
//
// Set to non-null on the task creation, and set to null when taking the
// result or cancelling the task.
RefPtr<CompileOrDecodeTask> mCompileOrDecodeTask;
// Runnable that is dispatched to the main thread when off-thread compilation
// completes.

View File

@ -23,19 +23,21 @@
#include "js/ColumnNumber.h" // #include "js/CompilationAndEvaluation.h"
#include "js/CompilationAndEvaluation.h"
#include "js/ContextOptions.h" // JS::ContextOptionsRef
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/CompileOptions.h" // JS::CompileOptions, JS::OwningCompileOptions, JS::DecodeOptions, JS::OwningDecodeOptions, JS::DelazificationOption
#include "js/ContextOptions.h" // JS::ContextOptionsRef
#include "js/experimental/JSStencil.h" // JS::Stencil, JS::InstantiationStorage
#include "js/experimental/CompileScript.h" // JS::FrontendContext, JS::NewFrontendContext, JS::DestroyFrontendContext, JS::SetNativeStackQuota, JS::CompilationStorage, JS::CompileGlobalScriptToStencil, JS::CompileModuleScriptToStencil, JS::DecodeStencil, JS::PrepareForInstantiate
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/loader/ScriptLoadRequest.h"
#include "ScriptCompression.h"
#include "js/loader/LoadedScript.h"
#include "js/loader/ModuleLoadRequest.h"
#include "js/MemoryFunctions.h"
#include "js/Modules.h"
#include "js/OffThreadScriptCompilation.h"
#include "js/PropertyAndElement.h" // JS_DefineProperty
#include "js/Realm.h"
#include "js/SourceText.h"
#include "js/Transcoding.h"
#include "js/Transcoding.h" // JS::TranscodeRange, JS::TranscodeResult, JS::IsTranscodeFailureResult
#include "js/Utility.h"
#include "xpcpublic.h"
#include "GeckoProfiler.h"
@ -51,6 +53,7 @@
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SRILogHelper.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/Mutex.h" // mozilla::Mutex
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_dom.h"
@ -89,12 +92,14 @@
#include "nsMimeTypes.h"
#include "mozilla/ConsoleReportCollector.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/EventQueue.h"
#include "mozilla/LoadInfo.h"
#include "ReferrerInfo.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/Attributes.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/TaskController.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
@ -1481,7 +1486,7 @@ nsresult ScriptLoader::CompileOffThreadOrProcessRequest(
NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
"Processing requests when running scripts is unsafe.");
if (!aRequest->GetScriptLoadContext()->mOffThreadToken &&
if (!aRequest->GetScriptLoadContext()->mCompileOrDecodeTask &&
!aRequest->GetScriptLoadContext()->CompileStarted()) {
bool couldCompile = false;
nsresult rv = AttemptOffThreadScriptCompile(aRequest, &couldCompile);
@ -1498,13 +1503,10 @@ nsresult ScriptLoader::CompileOffThreadOrProcessRequest(
return ProcessRequest(aRequest);
}
namespace {
class OffThreadCompilationCompleteRunnable : public Runnable {
nsMainThreadPtrHandle<ScriptLoadRequest> mRequest;
nsMainThreadPtrHandle<ScriptLoader> mLoader;
nsCOMPtr<nsISerialEventTarget> mEventTarget;
JS::OffThreadToken* mToken;
TimeStamp mStartTime;
TimeStamp mStopTime;
@ -1514,8 +1516,7 @@ class OffThreadCompilationCompleteRunnable : public Runnable {
: Runnable("dom::OffThreadCompilationCompleteRunnable"),
mRequest(
new nsMainThreadPtrHolder<ScriptLoadRequest>("mRequest", aRequest)),
mLoader(new nsMainThreadPtrHolder<ScriptLoader>("mLoader", aLoader)),
mToken(nullptr) {
mLoader(new nsMainThreadPtrHolder<ScriptLoader>("mLoader", aLoader)) {
MOZ_ASSERT(NS_IsMainThread());
if (DocGroup* docGroup = aLoader->GetDocGroup()) {
mEventTarget = docGroup->EventTargetFor(TaskCategory::Other);
@ -1525,11 +1526,6 @@ class OffThreadCompilationCompleteRunnable : public Runnable {
void RecordStartTime() { mStartTime = TimeStamp::Now(); }
void RecordStopTime() { mStopTime = TimeStamp::Now(); }
void SetToken(JS::OffThreadToken* aToken) {
MOZ_ASSERT(aToken && !mToken);
mToken = aToken;
}
static void Dispatch(
already_AddRefed<OffThreadCompilationCompleteRunnable>&& aSelf) {
RefPtr<OffThreadCompilationCompleteRunnable> self = aSelf;
@ -1540,8 +1536,6 @@ class OffThreadCompilationCompleteRunnable : public Runnable {
NS_DECL_NSIRUNNABLE
};
} /* anonymous namespace */
nsresult ScriptLoader::AttemptOffThreadScriptCompile(
ScriptLoadRequest* aRequest, bool* aCouldCompileOut) {
// If speculative parsing is enabled, the request may not be ready to run if
@ -1578,8 +1572,17 @@ nsresult ScriptLoader::AttemptOffThreadScriptCompile(
return rv;
}
// TODO: This uses the same heuristics and the same threshold as the
// JS::CanCompileOffThread / JS::CanDecodeOffThread APIs, but the
// heuristics needs to be updated to reflect the change regarding the
// Stencil API, and also the thread management on the consumer side
// (bug 1846160).
static constexpr size_t OffThreadMinimumTextLength = 5 * 1000;
static constexpr size_t OffThreadMinimumBytecodeLength = 5 * 1000;
if (aRequest->IsTextSource()) {
if (!JS::CanCompileOffThread(cx, options, aRequest->ScriptTextLength())) {
if (!StaticPrefs::javascript_options_parallel_parsing() ||
aRequest->ScriptTextLength() < OffThreadMinimumTextLength) {
TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
"scriptloader_main_thread_compile");
return NS_OK;
@ -1589,8 +1592,8 @@ nsresult ScriptLoader::AttemptOffThreadScriptCompile(
size_t length =
aRequest->mScriptBytecode.length() - aRequest->mBytecodeOffset;
JS::DecodeOptions decodeOptions(options);
if (!JS::CanDecodeOffThread(cx, decodeOptions, length)) {
if (!StaticPrefs::javascript_options_parallel_parsing() ||
length < OffThreadMinimumBytecodeLength) {
return NS_OK;
}
}
@ -1598,23 +1601,18 @@ nsresult ScriptLoader::AttemptOffThreadScriptCompile(
RefPtr<OffThreadCompilationCompleteRunnable> runnable =
new OffThreadCompilationCompleteRunnable(aRequest, this);
// Emulate dispatch. CompileOffThreadModule will call
// OffThreadCompilationCompleteCallback were we will emulate run.
LogRunnable::LogDispatch(runnable);
runnable->RecordStartTime();
JS::OffThreadToken* token = nullptr;
rv = StartOffThreadCompilation(cx, aRequest, options, runnable, &token);
rv = StartOffThreadCompilation(cx, aRequest, options, runnable);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(token);
aRequest->GetScriptLoadContext()->mOffThreadToken = token;
aRequest->GetScriptLoadContext()->mRunnable = runnable;
aRequest->GetScriptLoadContext()->BlockOnload(mDocument);
// Once the compilation is finished, a callback will dispatch the runnable to
// Once the compilation is finished, the runnable will be dispatched to
// the main thread to call ScriptLoader::ProcessOffThreadRequest for the
// request.
aRequest->mState = ScriptLoadRequest::State::Compiling;
@ -1635,22 +1633,239 @@ nsresult ScriptLoader::AttemptOffThreadScriptCompile(
return NS_OK;
}
static inline nsresult CompileResultForToken(void* aToken) {
return aToken ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
CompileOrDecodeTask::CompileOrDecodeTask(
OffThreadCompilationCompleteRunnable* aCompleteRunnable)
: Task(Kind::OffMainThreadOnly, EventQueuePriority::Normal),
mMutex("CompileOrDecodeTask"),
mOptions(JS::OwningCompileOptions::ForFrontendContext()),
mCompleteRunnable(aCompleteRunnable) {}
CompileOrDecodeTask::~CompileOrDecodeTask() {
if (mFrontendContext) {
JS::DestroyFrontendContext(mFrontendContext);
mFrontendContext = nullptr;
}
MOZ_ASSERT(!mCompleteRunnable);
}
nsresult CompileOrDecodeTask::InitFrontendContext() {
mFrontendContext = JS::NewFrontendContext();
if (!mFrontendContext) {
mCompleteRunnable = nullptr;
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
void CompileOrDecodeTask::DidRunTask(const MutexAutoLock& aProofOfLock,
RefPtr<JS::Stencil>&& aStencil) {
if (aStencil) {
if (!JS::PrepareForInstantiate(mFrontendContext, *aStencil,
mInstantiationStorage)) {
aStencil = nullptr;
}
}
mStencil = std::move(aStencil);
RefPtr<OffThreadCompilationCompleteRunnable> runnable = mCompleteRunnable;
mCompleteRunnable = nullptr;
LogRunnable::Run run(runnable);
runnable->RecordStopTime();
OffThreadCompilationCompleteRunnable::Dispatch(runnable.forget());
}
already_AddRefed<JS::Stencil> CompileOrDecodeTask::StealResult(
JSContext* aCx, JS::InstantiationStorage* aInstantiationStorage) {
JS::FrontendContext* fc = mFrontendContext;
mFrontendContext = nullptr;
auto destroyFrontendContext =
mozilla::MakeScopeExit([&]() { JS::DestroyFrontendContext(fc); });
MOZ_ASSERT(fc);
if (JS::HadFrontendErrors(fc)) {
(void)JS::ConvertFrontendErrorsToRuntimeErrors(aCx, fc, mOptions);
return nullptr;
}
if (!mStencil && JS::IsTranscodeFailureResult(mResult)) {
// Decode failure with bad content isn't reported as error.
JS_ReportErrorASCII(aCx, "failed to decode cache");
return nullptr;
}
// Report warnings.
if (!JS::ConvertFrontendErrorsToRuntimeErrors(aCx, fc, mOptions)) {
return nullptr;
}
MOZ_ASSERT(mStencil,
"If this task is cancelled, StealResult shouldn't be called");
// This task is started and finished successfully.
*aInstantiationStorage = std::move(mInstantiationStorage);
return mStencil.forget();
}
void CompileOrDecodeTask::Cancel() {
MOZ_ASSERT(NS_IsMainThread());
MutexAutoLock lock(mMutex);
mCompleteRunnable = nullptr;
}
enum class CompilationTarget { Script, Module };
template <CompilationTarget target>
class ScriptOrModuleCompileTask final : public CompileOrDecodeTask {
public:
ScriptOrModuleCompileTask(ScriptLoader::MaybeSourceText&& aMaybeSource,
OffThreadCompilationCompleteRunnable* aRunnable)
: CompileOrDecodeTask(aRunnable), mMaybeSource(std::move(aMaybeSource)) {}
nsresult Init(JS::CompileOptions& aOptions) {
nsresult rv = InitFrontendContext();
NS_ENSURE_SUCCESS(rv, rv);
if (!mOptions.copy(mFrontendContext, aOptions)) {
mCompleteRunnable = nullptr;
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
bool Run() override {
MutexAutoLock lock(mMutex);
if (IsCancelled(lock)) {
return true;
}
RefPtr<JS::Stencil> stencil = Compile();
DidRunTask(lock, std::move(stencil));
return true;
}
private:
already_AddRefed<JS::Stencil> Compile() {
JS::SetNativeStackQuota(mFrontendContext, kDefaultStackQuota);
JS::CompilationStorage compileStorage;
auto compile = [&](auto& source) {
if constexpr (target == CompilationTarget::Script) {
return JS::CompileGlobalScriptToStencil(mFrontendContext, mOptions,
source, compileStorage);
}
return JS::CompileModuleScriptToStencil(mFrontendContext, mOptions,
source, compileStorage);
};
return mMaybeSource.mapNonEmpty(compile);
}
public:
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
bool GetName(nsACString& aName) override {
if constexpr (target == CompilationTarget::Script) {
aName.AssignLiteral("ScriptCompileTask");
} else {
aName.AssignLiteral("ModuleCompileTask");
}
return true;
}
#endif
private:
ScriptLoader::MaybeSourceText mMaybeSource;
};
using ScriptCompileTask =
class ScriptOrModuleCompileTask<CompilationTarget::Script>;
using ModuleCompileTask =
class ScriptOrModuleCompileTask<CompilationTarget::Module>;
class ScriptDecodeTask final : public CompileOrDecodeTask {
public:
ScriptDecodeTask(const JS::TranscodeRange& aRange,
OffThreadCompilationCompleteRunnable* aRunnable)
: CompileOrDecodeTask(aRunnable), mRange(aRange) {}
nsresult Init(JS::DecodeOptions& aOptions) {
nsresult rv = InitFrontendContext();
NS_ENSURE_SUCCESS(rv, rv);
if (!mDecodeOptions.copy(mFrontendContext, aOptions)) {
mCompleteRunnable = nullptr;
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
bool Run() override {
MutexAutoLock lock(mMutex);
if (IsCancelled(lock)) {
return true;
}
RefPtr<JS::Stencil> stencil = Decode();
JS::OwningCompileOptions compileOptions(
(JS::OwningCompileOptions::ForFrontendContext()));
mOptions.steal(std::move(mDecodeOptions));
DidRunTask(lock, std::move(stencil));
return true;
}
private:
already_AddRefed<JS::Stencil> Decode() {
// NOTE: JS::DecodeStencil doesn't need the stack quota.
JS::CompilationStorage compileStorage;
RefPtr<JS::Stencil> stencil;
mResult = JS::DecodeStencil(mFrontendContext, mDecodeOptions, mRange,
getter_AddRefs(stencil));
return stencil.forget();
}
public:
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
bool GetName(nsACString& aName) override {
aName.AssignLiteral("ScriptDecodeTask");
return true;
}
#endif
private:
JS::OwningDecodeOptions mDecodeOptions;
JS::TranscodeRange mRange;
};
nsresult ScriptLoader::StartOffThreadCompilation(
JSContext* aCx, ScriptLoadRequest* aRequest, JS::CompileOptions& aOptions,
Runnable* aRunnable, JS::OffThreadToken** aTokenOut) {
const JS::OffThreadCompileCallback callback =
OffThreadCompilationCompleteCallback;
OffThreadCompilationCompleteRunnable* aRunnable) {
if (aRequest->IsBytecode()) {
JS::DecodeOptions decodeOptions(aOptions);
*aTokenOut = JS::DecodeStencilOffThread(
aCx, decodeOptions, aRequest->mScriptBytecode,
aRequest->mBytecodeOffset, callback, aRunnable);
return CompileResultForToken(*aTokenOut);
JS::TranscodeRange range(
aRequest->mScriptBytecode.begin() + aRequest->mBytecodeOffset,
aRequest->mScriptBytecode.length() - aRequest->mBytecodeOffset);
RefPtr<ScriptDecodeTask> decodeTask =
new ScriptDecodeTask(range, aRunnable);
nsresult rv = decodeTask->Init(decodeOptions);
NS_ENSURE_SUCCESS(rv, rv);
aRequest->GetScriptLoadContext()->mCompileOrDecodeTask = decodeTask;
TaskController::Get()->AddTask(decodeTask.forget());
return NS_OK;
}
MaybeSourceText maybeSource;
@ -1672,14 +1887,13 @@ nsresult ScriptLoader::StartOffThreadCompilation(
}
if (aRequest->IsModuleRequest()) {
auto compile = [&](auto& source) {
return JS::CompileModuleToStencilOffThread(aCx, aOptions, source,
callback, aRunnable);
};
MOZ_ASSERT(!maybeSource.empty());
*aTokenOut = maybeSource.mapNonEmpty(compile);
return CompileResultForToken(*aTokenOut);
RefPtr<ModuleCompileTask> compileTask =
new ModuleCompileTask(std::move(maybeSource), aRunnable);
rv = compileTask->Init(aOptions);
NS_ENSURE_SUCCESS(rv, rv);
aRequest->GetScriptLoadContext()->mCompileOrDecodeTask = compileTask;
TaskController::Get()->AddTask(compileTask.forget());
return NS_OK;
}
if (StaticPrefs::dom_expose_test_interfaces()) {
@ -1704,27 +1918,13 @@ nsresult ScriptLoader::StartOffThreadCompilation(
}
}
auto compile = [&](auto& source) {
return JS::CompileToStencilOffThread(aCx, aOptions, source, callback,
aRunnable);
};
MOZ_ASSERT(!maybeSource.empty());
*aTokenOut = maybeSource.mapNonEmpty(compile);
return CompileResultForToken(*aTokenOut);
}
void ScriptLoader::OffThreadCompilationCompleteCallback(
JS::OffThreadToken* aToken, void* aCallbackData) {
RefPtr<OffThreadCompilationCompleteRunnable> aRunnable =
static_cast<OffThreadCompilationCompleteRunnable*>(aCallbackData);
LogRunnable::Run run(aRunnable);
aRunnable->RecordStopTime();
aRunnable->SetToken(aToken);
OffThreadCompilationCompleteRunnable::Dispatch(aRunnable.forget());
RefPtr<ScriptCompileTask> compileTask =
new ScriptCompileTask(std::move(maybeSource), aRunnable);
rv = compileTask->Init(aOptions);
NS_ENSURE_SUCCESS(rv, rv);
aRequest->GetScriptLoadContext()->mCompileOrDecodeTask = compileTask;
TaskController::Get()->AddTask(compileTask.forget());
return NS_OK;
}
NS_IMETHODIMP
@ -1733,13 +1933,12 @@ OffThreadCompilationCompleteRunnable::Run() {
RefPtr<ScriptLoadContext> context = mRequest->GetScriptLoadContext();
MOZ_ASSERT_IF(context->mRunnable, context->mRunnable == this);
MOZ_ASSERT_IF(context->mOffThreadToken, context->mOffThreadToken == mToken);
// Clear the pointer to the runnable. The final reference will be released
// when this method returns.
context->mRunnable = nullptr;
if (!context->mOffThreadToken) {
if (!context->mCompileOrDecodeTask) {
// Request has been cancelled by MaybeCancelOffThreadScript.
return NS_OK;
}
@ -1783,7 +1982,7 @@ nsresult ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest) {
}
if (aRequest->IsModuleRequest()) {
MOZ_ASSERT(aRequest->GetScriptLoadContext()->mOffThreadToken);
MOZ_ASSERT(aRequest->GetScriptLoadContext()->mCompileOrDecodeTask);
ModuleLoadRequest* request = aRequest->AsModuleRequest();
return request->OnFetchComplete(NS_OK);
}
@ -1929,11 +2128,11 @@ nsresult ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest) {
mCurrentParserInsertedScript = oldParserInsertedScript;
}
if (aRequest->GetScriptLoadContext()->mOffThreadToken) {
if (aRequest->GetScriptLoadContext()->mCompileOrDecodeTask) {
// The request was parsed off-main-thread, but the result of the off
// thread parse was not actually needed to process the request
// (disappearing window, some other error, ...). Finish the
// request to avoid leaks in the JS engine.
// request to avoid leaks.
MOZ_ASSERT(!aRequest->IsModuleRequest());
aRequest->GetScriptLoadContext()->MaybeCancelOffThreadScript();
}
@ -2281,11 +2480,10 @@ nsresult ScriptLoader::CompileOrDecodeClassicScript(
nsresult rv;
if (aRequest->IsBytecode()) {
if (aRequest->GetScriptLoadContext()->mOffThreadToken) {
LOG(("ScriptLoadRequest (%p): Decode Bytecode & Join and Execute",
if (aRequest->GetScriptLoadContext()->mCompileOrDecodeTask) {
LOG(("ScriptLoadRequest (%p): Decode Bytecode & instantiate and Execute",
aRequest));
rv = aExec.JoinOffThread(
&aRequest->GetScriptLoadContext()->mOffThreadToken);
rv = aExec.JoinOffThread(aRequest->GetScriptLoadContext());
} else {
LOG(("ScriptLoadRequest (%p): Decode Bytecode and Execute", aRequest));
AUTO_PROFILER_MARKER_TEXT("BytecodeDecodeMainThread", JS,
@ -2305,15 +2503,14 @@ nsresult ScriptLoader::CompileOrDecodeClassicScript(
bool encodeBytecode = ShouldCacheBytecode(aRequest);
aExec.SetEncodeBytecode(encodeBytecode);
if (aRequest->GetScriptLoadContext()->mOffThreadToken) {
if (aRequest->GetScriptLoadContext()->mCompileOrDecodeTask) {
// Off-main-thread parsing.
LOG(
("ScriptLoadRequest (%p): Join (off-thread parsing) and "
("ScriptLoadRequest (%p): instantiate off-thread result and "
"Execute",
aRequest));
MOZ_ASSERT(aRequest->IsTextSource());
rv =
aExec.JoinOffThread(&aRequest->GetScriptLoadContext()->mOffThreadToken);
rv = aExec.JoinOffThread(aRequest->GetScriptLoadContext());
} else {
// Main thread parsing (inline and small scripts)
LOG(("ScriptLoadRequest (%p): Compile And Exec", aRequest));

View File

@ -104,6 +104,8 @@ class AsyncCompileShutdownObserver final : public nsIObserver {
// Script loader implementation
//////////////////////////////////////////////////////////////
class OffThreadCompilationCompleteRunnable;
class ScriptLoader final : public JS::loader::ScriptLoaderInterface {
class MOZ_STACK_CLASS AutoCurrentScriptUpdater {
public:
@ -132,6 +134,9 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface {
friend class AutoCurrentScriptUpdater;
public:
using MaybeSourceText =
mozilla::MaybeOneOf<JS::SourceText<char16_t>, JS::SourceText<Utf8Unit>>;
explicit ScriptLoader(Document* aDocument);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@ -569,14 +574,9 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface {
nsresult AttemptOffThreadScriptCompile(ScriptLoadRequest* aRequest,
bool* aCouldCompileOut);
nsresult StartOffThreadCompilation(JSContext* aCx,
ScriptLoadRequest* aRequest,
JS::CompileOptions& aOptions,
Runnable* aRunnable,
JS::OffThreadToken** aTokenOut);
static void OffThreadCompilationCompleteCallback(JS::OffThreadToken* aToken,
void* aCallbackData);
nsresult StartOffThreadCompilation(
JSContext* aCx, ScriptLoadRequest* aRequest, JS::CompileOptions& aOptions,
OffThreadCompilationCompleteRunnable* aRunnable);
nsresult ProcessRequest(ScriptLoadRequest* aRequest);
nsresult CompileOffThreadOrProcessRequest(ScriptLoadRequest* aRequest);
@ -678,9 +678,6 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface {
void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest);
using MaybeSourceText =
mozilla::MaybeOneOf<JS::SourceText<char16_t>, JS::SourceText<Utf8Unit>>;
// Returns wether we should save the bytecode of this script after the
// execution of the script.
static bool ShouldCacheBytecode(ScriptLoadRequest* aRequest);

View File

@ -1259,7 +1259,7 @@ nsresult ModuleLoaderBase::EvaluateModuleInContext(
ModuleLoadRequest* request = aRequest->AsModuleRequest();
MOZ_ASSERT(request->mModuleScript);
MOZ_ASSERT_IF(request->HasScriptLoadContext(),
!request->GetScriptLoadContext()->mOffThreadToken);
!request->GetScriptLoadContext()->mCompileOrDecodeTask);
ModuleScript* moduleScript = request->mModuleScript;
if (moduleScript->HasErrorToRethrow()) {

View File

@ -89,6 +89,11 @@ struct InstantiationStorage {
~InstantiationStorage();
void operator=(InstantiationStorage&& other) {
gcOutput_ = other.gcOutput_;
other.gcOutput_ = nullptr;
}
private:
InstantiationStorage(const InstantiationStorage& other) = delete;
void operator=(const InstantiationStorage& aOther) = delete;