Bug 1837964 - Part 4: Rewrite off-thread case of nsXULPrototypeScript::CompileMaybeOffThread with JS::FrontendContext APIs. r=bthrall,smaug

Differential Revision: https://phabricator.services.mozilla.com/D181206
This commit is contained in:
Tooru Fujisawa 2023-07-06 09:37:19 +00:00
Родитель af843d8fd0
Коммит 620121280b
1 изменённых файлов: 200 добавлений и 42 удалений

Просмотреть файл

@ -22,7 +22,8 @@
#include "XULTreeElement.h"
#include "js/CompilationAndEvaluation.h"
#include "js/CompileOptions.h"
#include "js/experimental/JSStencil.h"
#include "js/experimental/CompileScript.h" // JS::NewFrontendContext, JS::DestroyFrontendContext, JS::SetNativeStackQuota, JS::CompileGlobalScriptToStencil, JS::CompilationStorage
#include "js/experimental/JSStencil.h" // JS::Stencil, JS::FrontendContext
#include "js/OffThreadScriptCompilation.h"
#include "js/SourceText.h"
#include "js/Transcoding.h"
@ -46,7 +47,9 @@
#include "mozilla/RefPtr.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticAnalysisFunctions.h"
#include "mozilla/StaticPrefs_javascript.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/URLExtraData.h"
#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/BorrowedAttrInfo.h"
@ -1791,15 +1794,19 @@ class NotifyOffThreadScriptCompletedRunnable : public Runnable {
sReceivers;
static bool sSetupClearOnShutdown;
// Not-owning-pointer for the receiver.
//
// The pointed nsIOffThreadScriptReceiver is kept alive by sReceivers above.
nsIOffThreadScriptReceiver* mReceiver;
JS::OffThreadToken* mToken;
RefPtr<JS::Stencil> mStencil;
public:
NotifyOffThreadScriptCompletedRunnable(nsIOffThreadScriptReceiver* aReceiver,
JS::OffThreadToken* aToken)
RefPtr<JS::Stencil>&& aStencil)
: mozilla::Runnable("NotifyOffThreadScriptCompletedRunnable"),
mReceiver(aReceiver),
mToken(aToken) {}
mStencil(std::move(aStencil)) {}
static void NoteReceiver(nsIOffThreadScriptReceiver* aReceiver) {
if (!sSetupClearOnShutdown) {
@ -1824,18 +1831,6 @@ NS_IMETHODIMP
NotifyOffThreadScriptCompletedRunnable::Run() {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<JS::Stencil> stencil;
{
AutoJSAPI jsapi;
if (!jsapi.Init(xpc::CompilationScope())) {
// Now what? I guess we just leak... this should probably never
// happen.
return NS_ERROR_UNEXPECTED;
}
JSContext* cx = jsapi.cx();
stencil = JS::FinishOffThreadStencil(cx, mToken);
}
if (!sReceivers) {
// We've already shut down.
return NS_OK;
@ -1847,19 +1842,174 @@ NotifyOffThreadScriptCompletedRunnable::Run() {
std::move((*sReceivers)[index]);
sReceivers->RemoveElementAt(index);
return receiver->OnScriptCompileComplete(stencil,
stencil ? NS_OK : NS_ERROR_FAILURE);
return receiver->OnScriptCompileComplete(mStencil,
mStencil ? NS_OK : NS_ERROR_FAILURE);
}
static void OffThreadScriptReceiverCallback(JS::OffThreadToken* aToken,
void* aCallbackData) {
// Be careful not to adjust the refcount on the receiver, as this callback
// may be invoked off the main thread.
nsIOffThreadScriptReceiver* aReceiver =
static_cast<nsIOffThreadScriptReceiver*>(aCallbackData);
#ifdef DEBUG
static void CheckErrorsAndWarnings(JS::FrontendContext* aFc) {
if (JS::HadFrontendErrors(aFc)) {
const JSErrorReport* report = JS::GetFrontendErrorReport(aFc);
if (report) {
const char* message = "<unknown>";
const char* filename = "<unknown>";
if (report->message().c_str()) {
message = report->message().c_str();
}
if (report->filename.c_str()) {
filename = report->filename.c_str();
}
NS_WARNING(
nsPrintfCString(
"Had compilation error in ScriptCompileTask: %s at %s:%u:%u",
message, filename, report->lineno, report->column)
.get());
}
if (JS::HadFrontendOverRecursed(aFc)) {
NS_WARNING("Had over recursed in ScriptCompileTask");
}
if (JS::HadFrontendOutOfMemory(aFc)) {
NS_WARNING("Had out of memory in ScriptCompileTask");
}
if (JS::HadFrontendAllocationOverflow(aFc)) {
NS_WARNING("Had allocation overflow in ScriptCompileTask");
}
}
size_t count = JS::GetFrontendWarningCount(aFc);
for (size_t i = 0; i < count; i++) {
const JSErrorReport* report = JS::GetFrontendWarningAt(aFc, i);
const char* message = "<unknown>";
const char* filename = "<unknown>";
if (report->message().c_str()) {
message = report->message().c_str();
}
if (report->filename.c_str()) {
filename = report->filename.c_str();
}
NS_WARNING(
nsPrintfCString(
"Had compilation warning in ScriptCompileTask: %s at %s:%u:%u",
message, filename, report->lineno, report->column)
.get());
}
}
#endif
class ScriptCompileRunnable final : public Runnable {
public:
explicit ScriptCompileRunnable(UniquePtr<Utf8Unit[], JS::FreePolicy>&& aText,
size_t aTextLength,
nsIOffThreadScriptReceiver* aReceiver)
: Runnable("ScriptCompileRunnable"),
mOptions(JS::OwningCompileOptions::ForFrontendContext()),
mText(std::move(aText)),
mTextLength(aTextLength),
mReceiver(aReceiver) {}
~ScriptCompileRunnable() {
if (mFrontendContext) {
JS::DestroyFrontendContext(mFrontendContext);
}
}
nsresult Init(JS::CompileOptions& aOptions) {
mFrontendContext = JS::NewFrontendContext();
if (!mFrontendContext) {
return NS_ERROR_FAILURE;
}
if (!mOptions.copy(mFrontendContext, aOptions)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
RefPtr<JS::Stencil> Compile() {
// NOTE: The stack limit must be set from the same thread that compiles.
const size_t kDefaultStackQuota = 128 * sizeof(size_t) * 1024;
JS::SetNativeStackQuota(mFrontendContext, kDefaultStackQuota);
JS::SourceText<Utf8Unit> srcBuf;
if (NS_WARN_IF(!srcBuf.init(mFrontendContext, mText.get(), mTextLength,
JS::SourceOwnership::Borrowed))) {
return nullptr;
}
JS::CompilationStorage compileStorage;
RefPtr<JS::Stencil> stencil = JS::CompileGlobalScriptToStencil(
mFrontendContext, mOptions, srcBuf, compileStorage);
#ifdef DEBUG
// Chrome-privileged code shouldn't have any compilation error.
CheckErrorsAndWarnings(mFrontendContext);
MOZ_ASSERT(stencil);
#endif
if (NS_WARN_IF(!stencil)) {
return nullptr;
}
return stencil;
}
NS_DECL_NSIRUNNABLE
private:
// Owning-pointer for the context associated with the script compilation.
//
// The context is allocated on main thread in Init method, and is freed on
// any thread in the destructor.
JS::FrontendContext* mFrontendContext = nullptr;
JS::OwningCompileOptions mOptions;
// The source text for this compilation.
UniquePtr<Utf8Unit[], JS::FreePolicy> mText;
size_t mTextLength;
// Not-owning-pointer for the receiver which is going to be passed to
// NotifyOffThreadScriptCompletedRunnable.
//
// The pointed nsIOffThreadScriptReceiver is kept alive by
// NotifyOffThreadScriptCompletedRunnable::sReceivers, in order to avoid
// touching the refcount from random thread.
nsIOffThreadScriptReceiver* mReceiver;
};
NS_IMETHODIMP
ScriptCompileRunnable::Run() {
RefPtr<JS::Stencil> stencil = Compile();
// NOTE: Failure case is handled by
// NotifyOffThreadScriptCompletedRunnable::Run.
// Be careful not to adjust the refcount of mReceiver here, as this method is
// invoked off the main thread.
RefPtr<NotifyOffThreadScriptCompletedRunnable> notify =
new NotifyOffThreadScriptCompletedRunnable(aReceiver, aToken);
new NotifyOffThreadScriptCompletedRunnable(mReceiver, std::move(stencil));
NS_DispatchToMainThread(notify);
return NS_OK;
}
nsresult StartOffThreadCompile(JS::CompileOptions& aOptions,
UniquePtr<Utf8Unit[], JS::FreePolicy>&& aText,
size_t aTextLength,
nsIOffThreadScriptReceiver* aOffThreadReceiver) {
RefPtr<ScriptCompileRunnable> compile = new ScriptCompileRunnable(
std::move(aText), aTextLength, aOffThreadReceiver);
nsresult rv = compile->Init(aOptions);
NS_ENSURE_SUCCESS(rv, rv);
return NS_DispatchBackgroundTask(compile.forget());
}
nsresult nsXULPrototypeScript::Compile(const char16_t* aText,
@ -1901,35 +2051,43 @@ nsresult nsXULPrototypeScript::CompileMaybeOffThread(
nsIOffThreadScriptReceiver* aOffThreadReceiver) {
MOZ_ASSERT(aOffThreadReceiver);
AutoJSAPI jsapi;
if (!jsapi.Init(xpc::CompilationScope())) {
return NS_ERROR_UNEXPECTED;
}
JSContext* cx = jsapi.cx();
JS::SourceText<mozilla::Utf8Unit> srcBuf;
if (NS_WARN_IF(!srcBuf.init(cx, std::move(aText), aTextLength))) {
return NS_ERROR_FAILURE;
}
nsAutoCString urlspec;
nsresult rv = aURI->GetSpec(urlspec);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
AutoJSAPI jsapi;
if (!jsapi.Init(xpc::CompilationScope())) {
return NS_ERROR_UNEXPECTED;
}
JSContext* cx = jsapi.cx();
JS::CompileOptions options(cx);
FillCompileOptions(options, urlspec.get(), aLineNo);
if (JS::CanCompileOffThread(cx, options, aTextLength)) {
if (!JS::CompileToStencilOffThread(
cx, options, srcBuf, OffThreadScriptReceiverCallback,
static_cast<void*>(aOffThreadReceiver))) {
JS_ClearPendingException(cx);
return NS_ERROR_OUT_OF_MEMORY;
// TODO: This uses the same heuristics and the same threshold as the
// JS::CanDecodeOffThread API, 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 1840831).
static constexpr size_t OffThreadMinimumTextLength = 5 * 1000;
if (StaticPrefs::javascript_options_parallel_parsing() &&
aTextLength >= OffThreadMinimumTextLength) {
rv = StartOffThreadCompile(options, std::move(aText), aTextLength,
aOffThreadReceiver);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
NotifyOffThreadScriptCompletedRunnable::NoteReceiver(aOffThreadReceiver);
} else {
JS::SourceText<Utf8Unit> srcBuf;
if (NS_WARN_IF(!srcBuf.init(cx, aText.get(), aTextLength,
JS::SourceOwnership::Borrowed))) {
return NS_ERROR_FAILURE;
}
RefPtr<JS::Stencil> stencil =
JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
if (!stencil) {