Revert "(not to be confused with Basilisk-Dev's implementation) Backport Bugzilla 1379814 and 1431353."

This reverts commit b0cad39d1a.
This commit is contained in:
wuggy 2026-04-18 19:52:12 -07:00
commit 01553c6695
3 changed files with 128 additions and 283 deletions

View file

@ -16,7 +16,6 @@
#include "mozilla/ScopeExit.h"
#include "mozilla/SizePrintfMacros.h"
#include "mozilla/Sprintf.h"
#include "mozilla/Variant.h"
#include "mozilla/TimeStamp.h"
#ifdef XP_WIN
@ -174,213 +173,90 @@ enum class ScriptKind
Module
};
mozilla::Atomic<int32_t> gOffThreadJobSerial(1);
class OffThreadJob;
class OffThreadState {
enum State {
RUNNING,
DONE,
IDLE, /* ready to work; no token, no source */
COMPILING, /* working; no token, have source */
DONE /* compilation done: have token and source */
};
public:
OffThreadState()
: monitor(mutexid::ShellOffThreadState)
{}
: monitor(mutexid::ShellOffThreadState),
state(IDLE),
token(),
source(nullptr)
{ }
~OffThreadState()
bool startIfIdle(JSContext* cx, ScriptKind kind,
ScopedJSFreePtr<char16_t>& newSource)
{
MOZ_ASSERT(jobs.empty());
AutoLockMonitor alm(monitor);
if (state != IDLE)
return false;
MOZ_ASSERT(!token);
source = newSource.forget();
scriptKind = kind;
state = COMPILING;
return true;
}
using Source = JS::UniqueTwoByteChars;
void abandon(JSContext* cx) {
AutoLockMonitor alm(monitor);
MOZ_ASSERT(state == COMPILING);
MOZ_ASSERT(!token);
MOZ_ASSERT(source);
OffThreadJob* newJob(JSContext* cx, ScriptKind kind, Source&& source);
OffThreadJob* getSingleJob(JSContext* cx, ScriptKind kind);
OffThreadJob* lookupJobByID(JSContext* cx, ScriptKind kind, int32_t id);
OffThreadJob* lookupJobForArgs(JSContext* cx, ScriptKind kind, const CallArgs& args,
size_t arg);
void deleteJob(JSContext* cx, OffThreadJob* job);
void cancelAll(JSContext* cx);
js_free(source);
source = nullptr;
private:
friend class OffThreadJob;
Monitor monitor;
Vector<OffThreadJob*, 0, SystemAllocPolicy> jobs;
};
class OffThreadJob {
public:
using Source = OffThreadState::Source;
OffThreadJob(Monitor& monitor, ScriptKind kind, Source&& source)
: id(gOffThreadJobSerial++),
kind(kind),
monitor(monitor),
state(RUNNING),
token(nullptr),
source(Move(source))
{
MOZ_RELEASE_ASSERT(id > 0, "Off-thread job IDs exhausted");
}
~OffThreadJob() {
MOZ_ASSERT(state != RUNNING);
state = IDLE;
}
void markDone(void* newToken) {
AutoLockMonitor alm(monitor);
MOZ_ASSERT(state == RUNNING);
MOZ_ASSERT(state == COMPILING);
MOZ_ASSERT(!token);
MOZ_ASSERT(source);
MOZ_ASSERT(newToken);
token = newToken;
state = DONE;
alm.notifyAll();
alm.notify();
}
void* waitUntilDone(JSContext* cx) {
void* waitUntilDone(JSContext* cx, ScriptKind kind) {
AutoLockMonitor alm(monitor);
MOZ_ASSERT(state != RUNNING || token == nullptr);
if (state == IDLE || scriptKind != kind)
return nullptr;
while (state != DONE)
alm.wait();
if (state == COMPILING) {
while (state != DONE)
alm.wait();
}
MOZ_ASSERT(source);
js_free(source);
source = nullptr;
MOZ_ASSERT(token);
return token;
void* holdToken = token;
token = nullptr;
state = IDLE;
return holdToken;
}
const char16_t* sourceChars() const {
return source.get();
}
const int32_t id;
const ScriptKind kind;
private:
Monitor& monitor;
Monitor monitor;
ScriptKind scriptKind;
State state;
void* token;
Source source;
char16_t* source;
};
OffThreadJob*
OffThreadState::newJob(JSContext* cx, ScriptKind kind, Source&& source)
{
UniquePtr<OffThreadJob> job(cx->new_<OffThreadJob>(monitor, kind, Move(source)));
if (!job)
return nullptr;
{
AutoLockMonitor alm(monitor);
if (!jobs.append(job.get())) {
JS_ReportErrorASCII(cx, "OOM adding off-thread job");
return nullptr;
}
}
return job.release();
}
OffThreadJob*
OffThreadState::getSingleJob(JSContext* cx, ScriptKind kind)
{
AutoLockMonitor alm(monitor);
if (jobs.empty()) {
JS_ReportErrorASCII(cx, "No off-thread jobs are pending");
return nullptr;
}
if (jobs.length() > 1) {
JS_ReportErrorASCII(cx, "Multiple off-thread jobs are pending: must specify job ID");
return nullptr;
}
OffThreadJob* job = jobs[0];
if (job->kind != kind) {
JS_ReportErrorASCII(cx, "Off-thread job is the wrong kind");
return nullptr;
}
return job;
}
OffThreadJob*
OffThreadState::lookupJobByID(JSContext* cx, ScriptKind kind, int32_t id)
{
if (id <= 0) {
JS_ReportErrorASCII(cx, "Bad off-thread job ID");
return nullptr;
}
AutoLockMonitor alm(monitor);
if (jobs.empty()) {
JS_ReportErrorASCII(cx, "No off-thread jobs are pending");
return nullptr;
}
OffThreadJob* job = nullptr;
for (auto someJob : jobs) {
if (someJob->id == id) {
job = someJob;
break;
}
}
if (!job) {
JS_ReportErrorASCII(cx, "Off-thread job not found");
return nullptr;
}
if (job->kind != kind) {
JS_ReportErrorASCII(cx, "Off-thread job is the wrong kind");
return nullptr;
}
return job;
}
OffThreadJob*
OffThreadState::lookupJobForArgs(JSContext* cx, ScriptKind kind, const CallArgs& args,
size_t arg)
{
if (args.length() <= arg)
return getSingleJob(cx, kind);
int32_t id = 0;
RootedValue value(cx, args[arg]);
if (!ToInt32(cx, value, &id))
return nullptr;
return lookupJobByID(cx, kind, id);
}
void
OffThreadState::deleteJob(JSContext* cx, OffThreadJob* job)
{
AutoLockMonitor alm(monitor);
for (size_t i = 0; i < jobs.length(); i++) {
if (jobs[i] == job) {
jobs.erase(&jobs[i]);
js_delete(job);
return;
}
}
MOZ_CRASH("Off-thread job not found");
}
void
OffThreadState::cancelAll(JSContext* cx)
{
AutoLockMonitor alm(monitor);
while (!jobs.empty())
js_delete(jobs.popCopy());
}
// Per-context shell state.
struct ShellContext
{
@ -3561,8 +3437,6 @@ WorkerMain(void* arg)
JS::SetEnqueuePromiseJobCallback(cx, nullptr);
sc->jobQueue.reset();
sc->offThreadState.cancelAll(cx);
KillWatchdog(cx);
JS_DestroyContext(cx);
@ -4550,8 +4424,8 @@ SyntaxParse(JSContext* cx, unsigned argc, Value* vp)
static void
OffThreadCompileScriptCallback(void* token, void* callbackData)
{
auto job = static_cast<OffThreadJob*>(callbackData);
job->markDone(token);
ShellContext* sc = static_cast<ShellContext*>(callbackData);
sc->offThreadState.markDone(token);
}
static bool
@ -4610,16 +4484,17 @@ OffThreadCompileScript(JSContext* cx, unsigned argc, Value* vp)
// Make sure we own the string's chars, so that they are not freed before
// the compilation is finished.
JS::UniqueTwoByteChars ownedChars;
ScopedJSFreePtr<char16_t> ownedChars;
if (stableChars.maybeGiveOwnershipToCaller()) {
ownedChars.reset(const_cast<char16_t*>(chars));
ownedChars = const_cast<char16_t*>(chars);
} else {
ownedChars.reset(cx->pod_malloc<char16_t>(length));
if (!ownedChars)
char16_t* copy = cx->pod_malloc<char16_t>(length);
if (!copy)
return false;
mozilla::PodCopy(ownedChars.get(), chars, length);
chars = ownedChars.get();
mozilla::PodCopy(copy, chars, length);
ownedChars = copy;
chars = copy;
}
if (!JS::CanCompileOffThread(cx, options, length)) {
@ -4628,18 +4503,20 @@ OffThreadCompileScript(JSContext* cx, unsigned argc, Value* vp)
}
ShellContext* sc = GetShellContext(cx);
OffThreadJob* job = sc->offThreadState.newJob(cx, ScriptKind::Script, Move(ownedChars));
if (!job)
return false;
if (!JS::CompileOffThread(cx, options, chars, length,
OffThreadCompileScriptCallback, job))
{
sc->offThreadState.deleteJob(cx, job);
if (!sc->offThreadState.startIfIdle(cx, ScriptKind::Script, ownedChars)) {
JS_ReportErrorASCII(cx, "called offThreadCompileScript without calling runOffThreadScript"
" to receive prior off-thread compilation");
return false;
}
args.rval().setInt32(job->id);
if (!JS::CompileOffThread(cx, options, chars, length,
OffThreadCompileScriptCallback, sc))
{
sc->offThreadState.abandon(cx);
return false;
}
args.rval().setUndefined();
return true;
}
@ -4652,14 +4529,11 @@ runOffThreadScript(JSContext* cx, unsigned argc, Value* vp)
gc::FinishGC(cx);
ShellContext* sc = GetShellContext(cx);
OffThreadJob* job = sc->offThreadState.lookupJobForArgs(cx, ScriptKind::Script, args, 0);
if (!job)
void* token = sc->offThreadState.waitUntilDone(cx, ScriptKind::Script);
if (!token) {
JS_ReportErrorASCII(cx, "called runOffThreadScript when no compilation is pending");
return false;
void* token = job->waitUntilDone(cx);
MOZ_ASSERT(token);
sc->offThreadState.deleteJob(cx, job);
}
RootedScript script(cx, JS::FinishOffThreadScript(cx, token));
if (!script)
@ -4697,16 +4571,17 @@ OffThreadCompileModule(JSContext* cx, unsigned argc, Value* vp)
// Make sure we own the string's chars, so that they are not freed before
// the compilation is finished.
JS::UniqueTwoByteChars ownedChars;
ScopedJSFreePtr<char16_t> ownedChars;
if (stableChars.maybeGiveOwnershipToCaller()) {
ownedChars.reset(const_cast<char16_t*>(chars));
ownedChars = const_cast<char16_t*>(chars);
} else {
ownedChars.reset(cx->pod_malloc<char16_t>(length));
if (!ownedChars)
char16_t* copy = cx->pod_malloc<char16_t>(length);
if (!copy)
return false;
mozilla::PodCopy(ownedChars.get(), chars, length);
chars = ownedChars.get();
mozilla::PodCopy(copy, chars, length);
ownedChars = copy;
chars = copy;
}
if (!JS::CanCompileOffThread(cx, options, length)) {
@ -4715,18 +4590,20 @@ OffThreadCompileModule(JSContext* cx, unsigned argc, Value* vp)
}
ShellContext* sc = GetShellContext(cx);
OffThreadJob* job = sc->offThreadState.newJob(cx, ScriptKind::Module, Move(ownedChars));
if (!job)
return false;
if (!JS::CompileOffThreadModule(cx, options, chars, length,
OffThreadCompileScriptCallback, job))
{
sc->offThreadState.deleteJob(cx, job);
if (!sc->offThreadState.startIfIdle(cx, ScriptKind::Module, ownedChars)) {
JS_ReportErrorASCII(cx, "called offThreadCompileModule without receiving prior off-thread "
"compilation");
return false;
}
args.rval().setInt32(job->id);
if (!JS::CompileOffThreadModule(cx, options, chars, length,
OffThreadCompileScriptCallback, sc))
{
sc->offThreadState.abandon(cx);
return false;
}
args.rval().setUndefined();
return true;
}
@ -4739,14 +4616,11 @@ FinishOffThreadModule(JSContext* cx, unsigned argc, Value* vp)
gc::FinishGC(cx);
ShellContext* sc = GetShellContext(cx);
OffThreadJob* job = sc->offThreadState.lookupJobForArgs(cx, ScriptKind::Module, args, 0);
if (!job)
void* token = sc->offThreadState.waitUntilDone(cx, ScriptKind::Module);
if (!token) {
JS_ReportErrorASCII(cx, "called finishOffThreadModule when no compilation is pending");
return false;
void* token = job->waitUntilDone(cx);
MOZ_ASSERT(token);
sc->offThreadState.deleteJob(cx, job);
}
RootedObject module(cx, JS::FinishOffThreadModule(cx, token));
if (!module)
@ -6363,8 +6237,7 @@ static const JSFunctionSpecWithHelp shell_functions[] = {
JS_FN_HELP("offThreadCompileScript", OffThreadCompileScript, 1, 0,
"offThreadCompileScript(code[, options])",
" Compile |code| on a helper thread. To wait for the compilation to finish\n"
" and run the code, call |runOffThreadScript|. The function returns a job ID.\n"
" If present, |options| may\n"
" and run the code, call |runOffThreadScript|. If present, |options| may\n"
" have properties saying how the code should be compiled:\n"
" noScriptRval: use the no-script-rval compiler option (default: false)\n"
" fileName: filename for error messages and debug info\n"
@ -6379,22 +6252,19 @@ static const JSFunctionSpecWithHelp shell_functions[] = {
" Debugger.Source.prototype.elementAttributeName returns.\n"),
JS_FN_HELP("runOffThreadScript", runOffThreadScript, 0, 0,
"runOffThreadScript([jobID])",
" Wait for an off-thread compilation job to complete. The job ID can be\n"
" omitted if there is only one job pending. If an error occurred,\n"
"runOffThreadScript()",
" Wait for off-thread compilation to complete. If an error occurred,\n"
" throw the appropriate exception; otherwise, run the script and return\n"
" its value."),
JS_FN_HELP("offThreadCompileModule", OffThreadCompileModule, 1, 0,
"offThreadCompileModule(code)",
" Compile |code| on a helper thread, returning a job ID. To wait for the\n"
" compilation to finish and get the module object, call |finishOffThreadModule|\n"
" passing the job ID."),
" Compile |code| on a helper thread. To wait for the compilation to finish\n"
" and get the module object, call |finishOffThreadModule|."),
JS_FN_HELP("finishOffThreadModule", FinishOffThreadModule, 0, 0,
"finishOffThreadModule([jobID])",
" Wait for an off-thread compilation job to complete. The job ID can be\n"
" omitted if there is only one job pending. If an error occurred,\n"
"finishOffThreadModule()",
" Wait for off-thread compilation to complete. If an error occurred,\n"
" throw the appropriate exception; otherwise, return the module object"),
JS_FN_HELP("timeout", Timeout, 1, 0,
@ -8438,8 +8308,6 @@ main(int argc, char** argv, char** envp)
DestructSharedArrayBufferMailbox();
sc->offThreadState.cancelAll(cx);
JS_DestroyContext(cx);
JS_ShutDown();
return result;

View file

@ -860,31 +860,19 @@ GlobalHelperThreadState::waitForAllThreads()
template <typename T>
bool
GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads, bool isMaster) const
GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads) const
{
MOZ_ASSERT(maxThreads > 0);
if (maxThreads >= threadCount)
return true;
size_t count = 0;
size_t idle = 0;
for (auto& thread : *threads) {
if (thread.currentTask.isSome()) {
if (thread.currentTask->is<T>())
count++;
} else {
idle++;
}
if (thread.currentTask.isSome() && thread.currentTask->is<T>())
count++;
if (count >= maxThreads)
return false;
}
MOZ_ASSERT(idle > 0);
if (isMaster && idle <= 1)
return false;
return true;
}
@ -1103,11 +1091,7 @@ GlobalHelperThreadState::pendingIonCompileHasSufficientPriority(
bool
GlobalHelperThreadState::canStartParseTask(const AutoLockHelperThreadState& lock)
{
// Parse tasks that end up compiling asm.js may use Wasm compilation threads
// to generate machine code. We do not know ahead of time which parse tasks
// will need those resources, so conservatively treat them as master tasks.
return !parseWorklist(lock).empty() &&
checkTaskThreadLimit<ParseTask*>(maxParseThreads(), /*isMaster=*/true);
return !parseWorklist(lock).empty() && checkTaskThreadLimit<ParseTask*>(maxParseThreads());
}
bool
@ -1908,56 +1892,49 @@ HelperThread::threadLoop()
while (true) {
MOZ_ASSERT(idle());
js::oom::ThreadType task = js::oom::THREAD_TYPE_NONE;
// Block until a task is available. Save the value of whether we are
// going to do an Ion compile, in case the value returned by the method
// changes.
bool ionCompile = false;
while (true) {
if (terminate)
return;
if (HelperThreadState().canStartGCParallelTask(lock))
task = js::oom::THREAD_TYPE_GCPARALLEL;
else if (HelperThreadState().canStartGCHelperTask(lock))
task = js::oom::THREAD_TYPE_GCHELPER;
else if (HelperThreadState().pendingIonCompileHasSufficientPriority(lock))
task = js::oom::THREAD_TYPE_ION;
else if (HelperThreadState().canStartWasmCompile(lock))
task = js::oom::THREAD_TYPE_ASMJS;
else if (HelperThreadState().canStartPromiseTask(lock))
task = js::oom::THREAD_TYPE_PROMISE_TASK;
else if (HelperThreadState().canStartParseTask(lock))
task = js::oom::THREAD_TYPE_PARSE;
else if (HelperThreadState().canStartCompressionTask(lock))
task = js::oom::THREAD_TYPE_COMPRESS;
if (task != js::oom::THREAD_TYPE_NONE)
if ((ionCompile = HelperThreadState().pendingIonCompileHasSufficientPriority(lock)) ||
HelperThreadState().canStartWasmCompile(lock) ||
HelperThreadState().canStartPromiseTask(lock) ||
HelperThreadState().canStartParseTask(lock) ||
HelperThreadState().canStartCompressionTask(lock) ||
HelperThreadState().canStartGCHelperTask(lock) ||
HelperThreadState().canStartGCParallelTask(lock))
{
break;
}
HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER);
}
js::oom::SetThreadType(task);
switch (task) {
case js::oom::THREAD_TYPE_GCPARALLEL:
handleGCParallelWorkload(lock);
break;
case js::oom::THREAD_TYPE_GCHELPER:
handleGCHelperWorkload(lock);
break;
case js::oom::THREAD_TYPE_ION:
if (ionCompile) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_ION);
handleIonWorkload(lock);
break;
case js::oom::THREAD_TYPE_ASMJS:
} else if (HelperThreadState().canStartWasmCompile(lock)) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_ASMJS);
handleWasmWorkload(lock);
break;
case js::oom::THREAD_TYPE_PROMISE_TASK:
} else if (HelperThreadState().canStartPromiseTask(lock)) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_PROMISE_TASK);
handlePromiseTaskWorkload(lock);
break;
case js::oom::THREAD_TYPE_PARSE:
} else if (HelperThreadState().canStartParseTask(lock)) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_PARSE);
handleParseWorkload(lock, stackLimit);
break;
case js::oom::THREAD_TYPE_COMPRESS:
} else if (HelperThreadState().canStartCompressionTask(lock)) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_COMPRESS);
handleCompressionWorkload(lock);
break;
default:
} else if (HelperThreadState().canStartGCHelperTask(lock)) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_GCHELPER);
handleGCHelperWorkload(lock);
} else if (HelperThreadState().canStartGCParallelTask(lock)) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_GCPARALLEL);
handleGCParallelWorkload(lock);
} else {
MOZ_CRASH("No task to perform");
}
js::oom::SetThreadType(js::oom::THREAD_TYPE_NONE);
}
}

View file

@ -256,7 +256,7 @@ class GlobalHelperThreadState
void waitForAllThreads();
template <typename T>
bool checkTaskThreadLimit(size_t maxThreads, bool isMaster = false) const;
bool checkTaskThreadLimit(size_t maxThreads) const;
private: