From c457938e8c7a0166fbb08d07adb842771a1c591e Mon Sep 17 00:00:00 2001 From: Tooru Fujisawa Date: Tue, 29 Aug 2023 12:07:18 +0000 Subject: [PATCH] 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 --- dom/base/JSExecutionContext.cpp | 9 +- dom/base/JSExecutionContext.h | 9 +- dom/script/ModuleLoader.cpp | 8 +- dom/script/ScriptLoadContext.cpp | 26 ++- dom/script/ScriptLoadContext.h | 97 +++++++- dom/script/ScriptLoader.cpp | 355 ++++++++++++++++++++++------- dom/script/ScriptLoader.h | 19 +- js/loader/ModuleLoaderBase.cpp | 2 +- js/public/experimental/JSStencil.h | 5 + 9 files changed, 400 insertions(+), 130 deletions(-) diff --git a/dom/base/JSExecutionContext.cpp b/dom/base/JSExecutionContext.cpp index 041af59a2e99..cd355127410f 100644 --- a/dom/base/JSExecutionContext.cpp +++ b/dom/base/JSExecutionContext.cpp @@ -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 stencil = - JS::FinishOffThreadStencil(mCx, *aOffThreadToken, &storage); - *aOffThreadToken = nullptr; // Mark the token as having been finished. + RefPtr stencil = aContext->StealOffThreadResult(mCx, &storage); if (!stencil) { mSkip = true; mRv = EvaluationExceptionToNSResult(mCx); diff --git a/dom/base/JSExecutionContext.h b/dom/base/JSExecutionContext.h index 7bfb42301a13..849f31efe7b5 100644 --- a/dom/base/JSExecutionContext.h +++ b/dom/base/JSExecutionContext.h @@ -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& aSrcBuf); diff --git a/dom/script/ModuleLoader.cpp b/dom/script/ModuleLoader.cpp index bdaffb9e6455..4aaf7b135430 100644 --- a/dom/script/ModuleLoader.cpp +++ b/dom/script/ModuleLoader.cpp @@ -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 aModuleOut) { if (aRequest->GetScriptLoadContext()->mWasCompiledOMT) { JS::InstantiationStorage storage; - RefPtr stencil = JS::FinishOffThreadStencil( - aCx, aRequest->GetScriptLoadContext()->mOffThreadToken, &storage); - - aRequest->GetScriptLoadContext()->mOffThreadToken = nullptr; - + RefPtr stencil = + aRequest->GetScriptLoadContext()->StealOffThreadResult(aCx, &storage); if (!stencil) { return NS_ERROR_FAILURE; } diff --git a/dom/script/ScriptLoadContext.cpp b/dom/script/ScriptLoadContext.cpp index c0f9fea5bf66..4f1ae4bbf011 100644 --- a/dom/script/ScriptLoadContext.cpp +++ b/dom/script/ScriptLoadContext.cpp @@ -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 ScriptLoadContext::StealOffThreadResult( + JSContext* aCx, JS::InstantiationStorage* aInstantiationStorage) { + RefPtr compileOrDecodeTask = + mCompileOrDecodeTask.forget(); + + return compileOrDecodeTask->StealResult(aCx, aInstantiationStorage); +} + } // namespace mozilla::dom diff --git a/dom/script/ScriptLoadContext.h b/dom/script/ScriptLoadContext.h index 61aa07466700..6298acff43ad 100644 --- a/dom/script/ScriptLoadContext.h +++ b/dom/script/ScriptLoadContext.h @@ -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&& 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 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 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 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 mCompileOrDecodeTask; // Runnable that is dispatched to the main thread when off-thread compilation // completes. diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp index 71c5215aa609..9cbf52c88520 100644 --- a/dom/script/ScriptLoader.cpp +++ b/dom/script/ScriptLoader.cpp @@ -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 mRequest; nsMainThreadPtrHandle mLoader; nsCOMPtr mEventTarget; - JS::OffThreadToken* mToken; TimeStamp mStartTime; TimeStamp mStopTime; @@ -1514,8 +1516,7 @@ class OffThreadCompilationCompleteRunnable : public Runnable { : Runnable("dom::OffThreadCompilationCompleteRunnable"), mRequest( new nsMainThreadPtrHolder("mRequest", aRequest)), - mLoader(new nsMainThreadPtrHolder("mLoader", aLoader)), - mToken(nullptr) { + mLoader(new nsMainThreadPtrHolder("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&& aSelf) { RefPtr 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 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&& aStencil) { + if (aStencil) { + if (!JS::PrepareForInstantiate(mFrontendContext, *aStencil, + mInstantiationStorage)) { + aStencil = nullptr; + } + } + + mStencil = std::move(aStencil); + + RefPtr runnable = mCompleteRunnable; + mCompleteRunnable = nullptr; + + LogRunnable::Run run(runnable); + + runnable->RecordStopTime(); + + OffThreadCompilationCompleteRunnable::Dispatch(runnable.forget()); +} + +already_AddRefed 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 +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 stencil = Compile(); + + DidRunTask(lock, std::move(stencil)); + return true; + } + + private: + already_AddRefed 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; +using ModuleCompileTask = + class ScriptOrModuleCompileTask; + +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 stencil = Decode(); + + JS::OwningCompileOptions compileOptions( + (JS::OwningCompileOptions::ForFrontendContext())); + mOptions.steal(std::move(mDecodeOptions)); + + DidRunTask(lock, std::move(stencil)); + return true; + } + + private: + already_AddRefed Decode() { + // NOTE: JS::DecodeStencil doesn't need the stack quota. + + JS::CompilationStorage compileStorage; + RefPtr 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 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 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 aRunnable = - static_cast(aCallbackData); - - LogRunnable::Run run(aRunnable); - - aRunnable->RecordStopTime(); - aRunnable->SetToken(aToken); - - OffThreadCompilationCompleteRunnable::Dispatch(aRunnable.forget()); + RefPtr 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 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)); diff --git a/dom/script/ScriptLoader.h b/dom/script/ScriptLoader.h index 2fadd7094a44..8411d1c9b20b 100644 --- a/dom/script/ScriptLoader.h +++ b/dom/script/ScriptLoader.h @@ -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>; + 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>; - // Returns wether we should save the bytecode of this script after the // execution of the script. static bool ShouldCacheBytecode(ScriptLoadRequest* aRequest); diff --git a/js/loader/ModuleLoaderBase.cpp b/js/loader/ModuleLoaderBase.cpp index 0a3db9ecfbb5..349d5c297cb4 100644 --- a/js/loader/ModuleLoaderBase.cpp +++ b/js/loader/ModuleLoaderBase.cpp @@ -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()) { diff --git a/js/public/experimental/JSStencil.h b/js/public/experimental/JSStencil.h index 08f99ddebcb6..a02689f547cd 100644 --- a/js/public/experimental/JSStencil.h +++ b/js/public/experimental/JSStencil.h @@ -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;