From 01553c66959c8c8318d217ff78cdbbd297e4a418 Mon Sep 17 00:00:00 2001 From: wuggy Date: Sat, 18 Apr 2026 19:52:12 -0700 Subject: [PATCH] Revert "(not to be confused with Basilisk-Dev's implementation) Backport Bugzilla 1379814 and 1431353." This reverts commit b0cad39d1a1a7e8ea68830be06ad84a6590092f1. --- js/src/shell/js.cpp | 338 +++++++++++------------------------- js/src/vm/HelperThreads.cpp | 91 ++++------ js/src/vm/HelperThreads.h | 2 +- 3 files changed, 138 insertions(+), 293 deletions(-) diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 7209dcaa68..2c3d7f7a1d 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -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 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& 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 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 job(cx->new_(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(callbackData); - job->markDone(token); + ShellContext* sc = static_cast(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 ownedChars; if (stableChars.maybeGiveOwnershipToCaller()) { - ownedChars.reset(const_cast(chars)); + ownedChars = const_cast(chars); } else { - ownedChars.reset(cx->pod_malloc(length)); - if (!ownedChars) + char16_t* copy = cx->pod_malloc(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 ownedChars; if (stableChars.maybeGiveOwnershipToCaller()) { - ownedChars.reset(const_cast(chars)); + ownedChars = const_cast(chars); } else { - ownedChars.reset(cx->pod_malloc(length)); - if (!ownedChars) + char16_t* copy = cx->pod_malloc(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; diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index f0996d404e..b264b3bb9c 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -860,31 +860,19 @@ GlobalHelperThreadState::waitForAllThreads() template 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()) - count++; - } else { - idle++; - } + if (thread.currentTask.isSome() && thread.currentTask->is()) + 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(maxParseThreads(), /*isMaster=*/true); + return !parseWorklist(lock).empty() && checkTaskThreadLimit(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); } } diff --git a/js/src/vm/HelperThreads.h b/js/src/vm/HelperThreads.h index 5a1cc02e9a..ebd4c9b9cb 100644 --- a/js/src/vm/HelperThreads.h +++ b/js/src/vm/HelperThreads.h @@ -256,7 +256,7 @@ class GlobalHelperThreadState void waitForAllThreads(); template - bool checkTaskThreadLimit(size_t maxThreads, bool isMaster = false) const; + bool checkTaskThreadLimit(size_t maxThreads) const; private: