mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-09-26 23:54:56 +00:00
1644 lines
50 KiB
C++
1644 lines
50 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "vm/HelperThreads.h"
|
|
|
|
#include "mozilla/DebugOnly.h"
|
|
|
|
#include "jsnativestack.h"
|
|
#include "jsnum.h" // For FIX_FPU()
|
|
|
|
#include "asmjs/WasmIonCompile.h"
|
|
#include "frontend/BytecodeCompiler.h"
|
|
#include "gc/GCInternals.h"
|
|
#include "jit/IonBuilder.h"
|
|
#include "vm/Debugger.h"
|
|
#include "vm/Time.h"
|
|
#include "vm/TraceLogging.h"
|
|
|
|
#include "jscntxtinlines.h"
|
|
#include "jscompartmentinlines.h"
|
|
#include "jsobjinlines.h"
|
|
#include "jsscriptinlines.h"
|
|
|
|
using namespace js;
|
|
|
|
using mozilla::ArrayLength;
|
|
using mozilla::DebugOnly;
|
|
|
|
namespace js {
|
|
|
|
GlobalHelperThreadState* gHelperThreadState = nullptr;
|
|
|
|
} // namespace js
|
|
|
|
bool
|
|
js::CreateHelperThreadsState()
|
|
{
|
|
MOZ_ASSERT(!gHelperThreadState);
|
|
gHelperThreadState = js_new<GlobalHelperThreadState>();
|
|
return gHelperThreadState != nullptr;
|
|
}
|
|
|
|
void
|
|
js::DestroyHelperThreadsState()
|
|
{
|
|
MOZ_ASSERT(gHelperThreadState);
|
|
gHelperThreadState->finish();
|
|
js_delete(gHelperThreadState);
|
|
gHelperThreadState = nullptr;
|
|
}
|
|
|
|
bool
|
|
js::EnsureHelperThreadsInitialized()
|
|
{
|
|
MOZ_ASSERT(gHelperThreadState);
|
|
return gHelperThreadState->ensureInitialized();
|
|
}
|
|
|
|
static size_t
|
|
ThreadCountForCPUCount(size_t cpuCount)
|
|
{
|
|
// Create additional threads on top of the number of cores available, to
|
|
// provide some excess capacity in case threads pause each other.
|
|
static const uint32_t EXCESS_THREADS = 4;
|
|
return cpuCount + EXCESS_THREADS;
|
|
}
|
|
|
|
void
|
|
js::SetFakeCPUCount(size_t count)
|
|
{
|
|
// This must be called before the threads have been initialized.
|
|
MOZ_ASSERT(!HelperThreadState().threads);
|
|
|
|
HelperThreadState().cpuCount = count;
|
|
HelperThreadState().threadCount = ThreadCountForCPUCount(count);
|
|
}
|
|
|
|
bool
|
|
js::StartOffThreadWasmCompile(ExclusiveContext* cx, wasm::CompileTask* task)
|
|
{
|
|
AutoLockHelperThreadState lock;
|
|
|
|
// Don't append this task if another failed.
|
|
if (HelperThreadState().wasmFailed())
|
|
return false;
|
|
|
|
if (!HelperThreadState().wasmWorklist().append(task))
|
|
return false;
|
|
|
|
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
js::StartOffThreadIonCompile(JSContext* cx, jit::IonBuilder* builder)
|
|
{
|
|
AutoLockHelperThreadState lock;
|
|
|
|
if (!HelperThreadState().ionWorklist().append(builder))
|
|
return false;
|
|
|
|
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Move an IonBuilder for which compilation has either finished, failed, or
|
|
* been cancelled into the global finished compilation list. All off thread
|
|
* compilations which are started must eventually be finished.
|
|
*/
|
|
static void
|
|
FinishOffThreadIonCompile(jit::IonBuilder* builder)
|
|
{
|
|
AutoEnterOOMUnsafeRegion oomUnsafe;
|
|
if (!HelperThreadState().ionFinishedList().append(builder))
|
|
oomUnsafe.crash("FinishOffThreadIonCompile");
|
|
}
|
|
|
|
static inline bool
|
|
CompiledScriptMatches(JSCompartment* compartment, JSScript* script, JSScript* target)
|
|
{
|
|
if (script)
|
|
return target == script;
|
|
if (compartment)
|
|
return target->compartment() == compartment;
|
|
return true;
|
|
}
|
|
|
|
void
|
|
js::CancelOffThreadIonCompile(JSCompartment* compartment, JSScript* script)
|
|
{
|
|
if (compartment && !compartment->jitCompartment())
|
|
return;
|
|
|
|
AutoLockHelperThreadState lock;
|
|
|
|
if (!HelperThreadState().threads)
|
|
return;
|
|
|
|
/* Cancel any pending entries for which processing hasn't started. */
|
|
GlobalHelperThreadState::IonBuilderVector& worklist = HelperThreadState().ionWorklist();
|
|
for (size_t i = 0; i < worklist.length(); i++) {
|
|
jit::IonBuilder* builder = worklist[i];
|
|
if (CompiledScriptMatches(compartment, script, builder->script())) {
|
|
FinishOffThreadIonCompile(builder);
|
|
HelperThreadState().remove(worklist, &i);
|
|
}
|
|
}
|
|
|
|
/* Wait for in progress entries to finish up. */
|
|
for (size_t i = 0; i < HelperThreadState().threadCount; i++) {
|
|
HelperThread& helper = HelperThreadState().threads[i];
|
|
while (helper.ionBuilder() &&
|
|
CompiledScriptMatches(compartment, script, helper.ionBuilder()->script()))
|
|
{
|
|
helper.ionBuilder()->cancel();
|
|
if (helper.pause) {
|
|
helper.pause = false;
|
|
HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE);
|
|
}
|
|
HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
|
|
}
|
|
}
|
|
|
|
/* Cancel code generation for any completed entries. */
|
|
GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList();
|
|
for (size_t i = 0; i < finished.length(); i++) {
|
|
jit::IonBuilder* builder = finished[i];
|
|
if (CompiledScriptMatches(compartment, script, builder->script())) {
|
|
jit::FinishOffThreadBuilder(nullptr, builder);
|
|
HelperThreadState().remove(finished, &i);
|
|
}
|
|
}
|
|
|
|
/* Cancel lazy linking for pending builders (attached to the ionScript). */
|
|
jit::IonBuilder* builder = HelperThreadState().ionLazyLinkList().getFirst();
|
|
while (builder) {
|
|
jit::IonBuilder* next = builder->getNext();
|
|
if (CompiledScriptMatches(compartment, script, builder->script())) {
|
|
builder->script()->baselineScript()->removePendingIonBuilder(builder->script());
|
|
jit::FinishOffThreadBuilder(nullptr, builder);
|
|
}
|
|
builder = next;
|
|
}
|
|
}
|
|
|
|
static const JSClass parseTaskGlobalClass = {
|
|
"internal-parse-task-global", JSCLASS_GLOBAL_FLAGS,
|
|
nullptr, nullptr, nullptr, nullptr,
|
|
nullptr, nullptr, nullptr, nullptr,
|
|
nullptr, nullptr, nullptr,
|
|
JS_GlobalObjectTraceHook
|
|
};
|
|
|
|
ParseTask::ParseTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal, JSContext* initCx,
|
|
const char16_t* chars, size_t length,
|
|
JS::OffThreadCompileCallback callback, void* callbackData)
|
|
: cx(cx), options(initCx), chars(chars), length(length),
|
|
alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
|
|
exclusiveContextGlobal(exclusiveContextGlobal),
|
|
callback(callback), callbackData(callbackData),
|
|
script(nullptr), sourceObject(nullptr),
|
|
errors(cx), overRecursed(false)
|
|
{
|
|
}
|
|
|
|
bool
|
|
ParseTask::init(JSContext* cx, const ReadOnlyCompileOptions& options)
|
|
{
|
|
if (!this->options.copy(cx, options))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ParseTask::activate(JSRuntime* rt)
|
|
{
|
|
rt->setUsedByExclusiveThread(exclusiveContextGlobal->zone());
|
|
cx->enterCompartment(exclusiveContextGlobal->compartment());
|
|
}
|
|
|
|
bool
|
|
ParseTask::finish(JSContext* cx)
|
|
{
|
|
if (sourceObject) {
|
|
RootedScriptSource sso(cx, sourceObject);
|
|
if (!ScriptSourceObject::initFromOptions(cx, sso, options))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ParseTask::~ParseTask()
|
|
{
|
|
// ParseTask takes over ownership of its input exclusive context.
|
|
js_delete(cx);
|
|
|
|
for (size_t i = 0; i < errors.length(); i++)
|
|
js_delete(errors[i]);
|
|
}
|
|
void
|
|
ParseTask::trace(JSTracer* trc)
|
|
{
|
|
if (!cx->runtimeMatches(trc->runtime()))
|
|
return;
|
|
|
|
TraceManuallyBarrieredEdge(trc, &exclusiveContextGlobal, "ParseTask::exclusiveContextGlobal");
|
|
if (script)
|
|
TraceManuallyBarrieredEdge(trc, &script, "ParseTask::script");
|
|
if (sourceObject)
|
|
TraceManuallyBarrieredEdge(trc, &sourceObject, "ParseTask::sourceObject");
|
|
}
|
|
|
|
void
|
|
js::CancelOffThreadParses(JSRuntime* rt)
|
|
{
|
|
AutoLockHelperThreadState lock;
|
|
|
|
if (!HelperThreadState().threads)
|
|
return;
|
|
|
|
// Instead of forcibly canceling pending parse tasks, just wait for all scheduled
|
|
// and in progress ones to complete. Otherwise the final GC may not collect
|
|
// everything due to zones being used off thread.
|
|
while (true) {
|
|
bool pending = false;
|
|
GlobalHelperThreadState::ParseTaskVector& worklist = HelperThreadState().parseWorklist();
|
|
for (size_t i = 0; i < worklist.length(); i++) {
|
|
ParseTask* task = worklist[i];
|
|
if (task->runtimeMatches(rt))
|
|
pending = true;
|
|
}
|
|
if (!pending) {
|
|
bool inProgress = false;
|
|
for (size_t i = 0; i < HelperThreadState().threadCount; i++) {
|
|
ParseTask* task = HelperThreadState().threads[i].parseTask();
|
|
if (task && task->runtimeMatches(rt))
|
|
inProgress = true;
|
|
}
|
|
if (!inProgress)
|
|
break;
|
|
}
|
|
HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
|
|
}
|
|
|
|
// Clean up any parse tasks which haven't been finished by the main thread.
|
|
GlobalHelperThreadState::ParseTaskVector& finished = HelperThreadState().parseFinishedList();
|
|
while (true) {
|
|
bool found = false;
|
|
for (size_t i = 0; i < finished.length(); i++) {
|
|
ParseTask* task = finished[i];
|
|
if (task->runtimeMatches(rt)) {
|
|
found = true;
|
|
AutoUnlockHelperThreadState unlock;
|
|
HelperThreadState().finishParseTask(/* maybecx = */ nullptr, rt, task);
|
|
}
|
|
}
|
|
if (!found)
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool
|
|
js::OffThreadParsingMustWaitForGC(JSRuntime* rt)
|
|
{
|
|
// Off thread parsing can't occur during incremental collections on the
|
|
// atoms compartment, to avoid triggering barriers. (Outside the atoms
|
|
// compartment, the compilation will use a new zone that is never
|
|
// collected.) If an atoms-zone GC is in progress, hold off on executing the
|
|
// parse task until the atoms-zone GC completes (see
|
|
// EnqueuePendingParseTasksAfterGC).
|
|
return rt->activeGCInAtomsZone();
|
|
}
|
|
|
|
static bool
|
|
EnsureConstructor(JSContext* cx, Handle<GlobalObject*> global, JSProtoKey key)
|
|
{
|
|
if (!GlobalObject::ensureConstructor(cx, global, key))
|
|
return false;
|
|
|
|
MOZ_ASSERT(global->getPrototype(key).toObject().isDelegate(),
|
|
"standard class prototype wasn't a delegate from birth");
|
|
return true;
|
|
}
|
|
|
|
// Initialize all classes potentially created during parsing for use in parser
|
|
// data structures, template objects, &c.
|
|
static bool
|
|
EnsureParserCreatedClasses(JSContext* cx)
|
|
{
|
|
Handle<GlobalObject*> global = cx->global();
|
|
|
|
if (!EnsureConstructor(cx, global, JSProto_Function))
|
|
return false; // needed by functions, also adds object literals' proto
|
|
|
|
if (!EnsureConstructor(cx, global, JSProto_Array))
|
|
return false; // needed by array literals
|
|
|
|
if (!EnsureConstructor(cx, global, JSProto_RegExp))
|
|
return false; // needed by regular expression literals
|
|
|
|
if (!EnsureConstructor(cx, global, JSProto_Iterator))
|
|
return false; // needed by ???
|
|
|
|
if (!GlobalObject::initStarGenerators(cx, global))
|
|
return false; // needed by function*() {} and generator comprehensions
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options,
|
|
const char16_t* chars, size_t length,
|
|
JS::OffThreadCompileCallback callback, void* callbackData)
|
|
{
|
|
// Suppress GC so that calls below do not trigger a new incremental GC
|
|
// which could require barriers on the atoms compartment.
|
|
gc::AutoSuppressGC suppress(cx);
|
|
|
|
JS::CompartmentOptions compartmentOptions(cx->compartment()->options());
|
|
compartmentOptions.setZone(JS::FreshZone);
|
|
compartmentOptions.setInvisibleToDebugger(true);
|
|
compartmentOptions.setMergeable(true);
|
|
|
|
// Don't falsely inherit the host's global trace hook.
|
|
compartmentOptions.setTrace(nullptr);
|
|
|
|
JSObject* global = JS_NewGlobalObject(cx, &parseTaskGlobalClass, nullptr,
|
|
JS::FireOnNewGlobalHook, compartmentOptions);
|
|
if (!global)
|
|
return false;
|
|
|
|
JS_SetCompartmentPrincipals(global->compartment(), cx->compartment()->principals());
|
|
|
|
// Initialize all classes required for parsing while still on the main
|
|
// thread, for both the target and the new global so that prototype
|
|
// pointers can be changed infallibly after parsing finishes.
|
|
if (!EnsureParserCreatedClasses(cx))
|
|
return false;
|
|
{
|
|
AutoCompartment ac(cx, global);
|
|
if (!EnsureParserCreatedClasses(cx))
|
|
return false;
|
|
}
|
|
|
|
ScopedJSDeletePtr<ExclusiveContext> helpercx(
|
|
cx->new_<ExclusiveContext>(cx->runtime(), (PerThreadData*) nullptr,
|
|
ExclusiveContext::Context_Exclusive));
|
|
if (!helpercx)
|
|
return false;
|
|
|
|
ScopedJSDeletePtr<ParseTask> task(
|
|
cx->new_<ParseTask>(helpercx.get(), global, cx, chars, length,
|
|
callback, callbackData));
|
|
if (!task)
|
|
return false;
|
|
|
|
helpercx.forget();
|
|
|
|
if (!task->init(cx, options))
|
|
return false;
|
|
|
|
if (OffThreadParsingMustWaitForGC(cx->runtime())) {
|
|
AutoLockHelperThreadState lock;
|
|
if (!HelperThreadState().parseWaitingOnGC().append(task.get())) {
|
|
ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
} else {
|
|
AutoLockHelperThreadState lock;
|
|
if (!HelperThreadState().parseWorklist().append(task.get())) {
|
|
ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
|
|
task->activate(cx->runtime());
|
|
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
|
|
}
|
|
|
|
task.forget();
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
js::EnqueuePendingParseTasksAfterGC(JSRuntime* rt)
|
|
{
|
|
MOZ_ASSERT(!OffThreadParsingMustWaitForGC(rt));
|
|
|
|
GlobalHelperThreadState::ParseTaskVector newTasks;
|
|
{
|
|
AutoLockHelperThreadState lock;
|
|
GlobalHelperThreadState::ParseTaskVector& waiting = HelperThreadState().parseWaitingOnGC();
|
|
|
|
for (size_t i = 0; i < waiting.length(); i++) {
|
|
ParseTask* task = waiting[i];
|
|
if (task->runtimeMatches(rt)) {
|
|
AutoEnterOOMUnsafeRegion oomUnsafe;
|
|
if (!newTasks.append(task))
|
|
oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");
|
|
HelperThreadState().remove(waiting, &i);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newTasks.empty())
|
|
return;
|
|
|
|
// This logic should mirror the contents of the !activeGCInAtomsZone()
|
|
// branch in StartOffThreadParseScript:
|
|
|
|
for (size_t i = 0; i < newTasks.length(); i++)
|
|
newTasks[i]->activate(rt);
|
|
|
|
AutoLockHelperThreadState lock;
|
|
|
|
{
|
|
AutoEnterOOMUnsafeRegion oomUnsafe;
|
|
if (!HelperThreadState().parseWorklist().appendAll(newTasks))
|
|
oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");
|
|
}
|
|
|
|
HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER);
|
|
}
|
|
|
|
static const uint32_t kDefaultHelperStackSize = 2048 * 1024;
|
|
static const uint32_t kDefaultHelperStackQuota = 1800 * 1024;
|
|
|
|
// TSan enforces a minimum stack size that's just slightly larger than our
|
|
// default helper stack size. It does this to store blobs of TSan-specific
|
|
// data on each thread's stack. Unfortunately, that means that even though
|
|
// we'll actually receive a larger stack than we requested, the effective
|
|
// usable space of that stack is significantly less than what we expect.
|
|
// To offset TSan stealing our stack space from underneath us, double the
|
|
// default.
|
|
//
|
|
// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't
|
|
// require all the thread-specific state that TSan does.
|
|
#if defined(MOZ_TSAN)
|
|
static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize;
|
|
static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota;
|
|
#else
|
|
static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize;
|
|
static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota;
|
|
#endif
|
|
|
|
bool
|
|
GlobalHelperThreadState::ensureInitialized()
|
|
{
|
|
MOZ_ASSERT(CanUseExtraThreads());
|
|
|
|
MOZ_ASSERT(this == &HelperThreadState());
|
|
AutoLockHelperThreadState lock;
|
|
|
|
if (threads)
|
|
return true;
|
|
|
|
threads = js_pod_calloc<HelperThread>(threadCount);
|
|
if (!threads)
|
|
return false;
|
|
|
|
for (size_t i = 0; i < threadCount; i++) {
|
|
HelperThread& helper = threads[i];
|
|
helper.threadData.emplace(static_cast<JSRuntime*>(nullptr));
|
|
helper.thread = PR_CreateThread(PR_USER_THREAD,
|
|
HelperThread::ThreadMain, &helper,
|
|
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, HELPER_STACK_SIZE);
|
|
if (!helper.thread || !helper.threadData->init()) {
|
|
finishThreads();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
GlobalHelperThreadState::GlobalHelperThreadState()
|
|
: cpuCount(0),
|
|
threadCount(0),
|
|
threads(nullptr),
|
|
wasmCompilationInProgress(false),
|
|
numWasmFailedJobs(0),
|
|
helperLock(nullptr),
|
|
consumerWakeup(nullptr),
|
|
producerWakeup(nullptr),
|
|
pauseWakeup(nullptr)
|
|
{
|
|
cpuCount = GetCPUCount();
|
|
threadCount = ThreadCountForCPUCount(cpuCount);
|
|
|
|
MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
|
|
|
|
helperLock = PR_NewLock();
|
|
consumerWakeup = PR_NewCondVar(helperLock);
|
|
producerWakeup = PR_NewCondVar(helperLock);
|
|
pauseWakeup = PR_NewCondVar(helperLock);
|
|
}
|
|
|
|
void
|
|
GlobalHelperThreadState::finish()
|
|
{
|
|
finishThreads();
|
|
|
|
PR_DestroyCondVar(consumerWakeup);
|
|
PR_DestroyCondVar(producerWakeup);
|
|
PR_DestroyCondVar(pauseWakeup);
|
|
PR_DestroyLock(helperLock);
|
|
|
|
ionLazyLinkList_.clear();
|
|
}
|
|
|
|
void
|
|
GlobalHelperThreadState::finishThreads()
|
|
{
|
|
if (!threads)
|
|
return;
|
|
|
|
MOZ_ASSERT(CanUseExtraThreads());
|
|
for (size_t i = 0; i < threadCount; i++)
|
|
threads[i].destroy();
|
|
js_free(threads);
|
|
threads = nullptr;
|
|
}
|
|
|
|
void
|
|
GlobalHelperThreadState::lock()
|
|
{
|
|
MOZ_ASSERT(!isLocked());
|
|
AssertCurrentThreadCanLock(HelperThreadStateLock);
|
|
PR_Lock(helperLock);
|
|
#ifdef DEBUG
|
|
lockOwner.value = PR_GetCurrentThread();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
GlobalHelperThreadState::unlock()
|
|
{
|
|
MOZ_ASSERT(isLocked());
|
|
#ifdef DEBUG
|
|
lockOwner.value = nullptr;
|
|
#endif
|
|
PR_Unlock(helperLock);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
bool
|
|
GlobalHelperThreadState::isLocked()
|
|
{
|
|
return lockOwner.value == PR_GetCurrentThread();
|
|
}
|
|
#endif
|
|
|
|
void
|
|
GlobalHelperThreadState::wait(CondVar which, uint32_t millis)
|
|
{
|
|
MOZ_ASSERT(isLocked());
|
|
#ifdef DEBUG
|
|
lockOwner.value = nullptr;
|
|
#endif
|
|
DebugOnly<PRStatus> status =
|
|
PR_WaitCondVar(whichWakeup(which),
|
|
millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
|
|
MOZ_ASSERT(status == PR_SUCCESS);
|
|
#ifdef DEBUG
|
|
lockOwner.value = PR_GetCurrentThread();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
GlobalHelperThreadState::notifyAll(CondVar which)
|
|
{
|
|
MOZ_ASSERT(isLocked());
|
|
PR_NotifyAllCondVar(whichWakeup(which));
|
|
}
|
|
|
|
void
|
|
GlobalHelperThreadState::notifyOne(CondVar which)
|
|
{
|
|
MOZ_ASSERT(isLocked());
|
|
PR_NotifyCondVar(whichWakeup(which));
|
|
}
|
|
|
|
bool
|
|
GlobalHelperThreadState::hasActiveThreads()
|
|
{
|
|
MOZ_ASSERT(isLocked());
|
|
if (!threads)
|
|
return false;
|
|
|
|
for (size_t i = 0; i < threadCount; i++) {
|
|
if (!threads[i].idle())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
GlobalHelperThreadState::waitForAllThreads()
|
|
{
|
|
CancelOffThreadIonCompile(nullptr, nullptr);
|
|
|
|
AutoLockHelperThreadState lock;
|
|
while (hasActiveThreads())
|
|
wait(CONSUMER);
|
|
}
|
|
|
|
template <typename T>
|
|
bool
|
|
GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads) const
|
|
{
|
|
if (maxThreads >= threadCount)
|
|
return true;
|
|
|
|
size_t count = 0;
|
|
for (size_t i = 0; i < threadCount; i++) {
|
|
if (threads[i].currentTask.isSome() && threads[i].currentTask->is<T>())
|
|
count++;
|
|
if (count >= maxThreads)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool
|
|
IsHelperThreadSimulatingOOM(js::oom::ThreadType threadType)
|
|
{
|
|
#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
|
|
return js::oom::targetThread == threadType;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
size_t
|
|
GlobalHelperThreadState::maxIonCompilationThreads() const
|
|
{
|
|
if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_ION))
|
|
return 1;
|
|
return threadCount;
|
|
}
|
|
|
|
size_t
|
|
GlobalHelperThreadState::maxUnpausedIonCompilationThreads() const
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
size_t
|
|
GlobalHelperThreadState::maxWasmCompilationThreads() const
|
|
{
|
|
if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_ASMJS))
|
|
return 1;
|
|
if (cpuCount < 2)
|
|
return 2;
|
|
return cpuCount;
|
|
}
|
|
|
|
size_t
|
|
GlobalHelperThreadState::maxParseThreads() const
|
|
{
|
|
if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_PARSE))
|
|
return 1;
|
|
|
|
// Don't allow simultaneous off thread parses, to reduce contention on the
|
|
// atoms table. Note that asm.js compilation depends on this to avoid
|
|
// stalling the helper thread, as off thread parse tasks can trigger and
|
|
// block on other off thread asm.js compilation tasks.
|
|
return 1;
|
|
}
|
|
|
|
size_t
|
|
GlobalHelperThreadState::maxCompressionThreads() const
|
|
{
|
|
if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_COMPRESS))
|
|
return 1;
|
|
return threadCount;
|
|
}
|
|
|
|
size_t
|
|
GlobalHelperThreadState::maxGCHelperThreads() const
|
|
{
|
|
if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_GCHELPER))
|
|
return 1;
|
|
return threadCount;
|
|
}
|
|
|
|
size_t
|
|
GlobalHelperThreadState::maxGCParallelThreads() const
|
|
{
|
|
if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_GCPARALLEL))
|
|
return 1;
|
|
return threadCount;
|
|
}
|
|
|
|
bool
|
|
GlobalHelperThreadState::canStartWasmCompile()
|
|
{
|
|
// Don't execute an wasm job if an earlier one failed.
|
|
MOZ_ASSERT(isLocked());
|
|
if (wasmWorklist().empty() || numWasmFailedJobs)
|
|
return false;
|
|
|
|
// Honor the maximum allowed threads to compile wasm jobs at once,
|
|
// to avoid oversaturating the machine.
|
|
if (!checkTaskThreadLimit<wasm::CompileTask*>(maxWasmCompilationThreads()))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
IonBuilderHasHigherPriority(jit::IonBuilder* first, jit::IonBuilder* second)
|
|
{
|
|
// This method can return whatever it wants, though it really ought to be a
|
|
// total order. The ordering is allowed to race (change on the fly), however.
|
|
|
|
// A lower optimization level indicates a higher priority.
|
|
if (first->optimizationInfo().level() != second->optimizationInfo().level())
|
|
return first->optimizationInfo().level() < second->optimizationInfo().level();
|
|
|
|
// A script without an IonScript has precedence on one with.
|
|
if (first->scriptHasIonScript() != second->scriptHasIonScript())
|
|
return !first->scriptHasIonScript();
|
|
|
|
// A higher warm-up counter indicates a higher priority.
|
|
return first->script()->getWarmUpCount() / first->script()->length() >
|
|
second->script()->getWarmUpCount() / second->script()->length();
|
|
}
|
|
|
|
bool
|
|
GlobalHelperThreadState::canStartIonCompile()
|
|
{
|
|
return !ionWorklist().empty() &&
|
|
checkTaskThreadLimit<jit::IonBuilder*>(maxIonCompilationThreads());
|
|
}
|
|
|
|
jit::IonBuilder*
|
|
GlobalHelperThreadState::highestPriorityPendingIonCompile(bool remove /* = false */)
|
|
{
|
|
MOZ_ASSERT(isLocked());
|
|
|
|
if (ionWorklist().empty()) {
|
|
MOZ_ASSERT(!remove);
|
|
return nullptr;
|
|
}
|
|
|
|
// Get the highest priority IonBuilder which has not started compilation yet.
|
|
size_t index = 0;
|
|
for (size_t i = 1; i < ionWorklist().length(); i++) {
|
|
if (IonBuilderHasHigherPriority(ionWorklist()[i], ionWorklist()[index]))
|
|
index = i;
|
|
}
|
|
jit::IonBuilder* builder = ionWorklist()[index];
|
|
if (remove)
|
|
ionWorklist().erase(&ionWorklist()[index]);
|
|
return builder;
|
|
}
|
|
|
|
HelperThread*
|
|
GlobalHelperThreadState::lowestPriorityUnpausedIonCompileAtThreshold()
|
|
{
|
|
MOZ_ASSERT(isLocked());
|
|
|
|
// Get the lowest priority IonBuilder which has started compilation and
|
|
// isn't paused, unless there are still fewer than the maximum number of
|
|
// such builders permitted.
|
|
size_t numBuilderThreads = 0;
|
|
HelperThread* thread = nullptr;
|
|
for (size_t i = 0; i < threadCount; i++) {
|
|
if (threads[i].ionBuilder() && !threads[i].pause) {
|
|
numBuilderThreads++;
|
|
if (!thread || IonBuilderHasHigherPriority(thread->ionBuilder(), threads[i].ionBuilder()))
|
|
thread = &threads[i];
|
|
}
|
|
}
|
|
if (numBuilderThreads < maxUnpausedIonCompilationThreads())
|
|
return nullptr;
|
|
return thread;
|
|
}
|
|
|
|
HelperThread*
|
|
GlobalHelperThreadState::highestPriorityPausedIonCompile()
|
|
{
|
|
MOZ_ASSERT(isLocked());
|
|
|
|
// Get the highest priority IonBuilder which has started compilation but
|
|
// which was subsequently paused.
|
|
HelperThread* thread = nullptr;
|
|
for (size_t i = 0; i < threadCount; i++) {
|
|
if (threads[i].pause) {
|
|
// Currently, only threads with IonBuilders can be paused.
|
|
MOZ_ASSERT(threads[i].ionBuilder());
|
|
if (!thread || IonBuilderHasHigherPriority(threads[i].ionBuilder(), thread->ionBuilder()))
|
|
thread = &threads[i];
|
|
}
|
|
}
|
|
return thread;
|
|
}
|
|
|
|
bool
|
|
GlobalHelperThreadState::pendingIonCompileHasSufficientPriority()
|
|
{
|
|
MOZ_ASSERT(isLocked());
|
|
|
|
// Can't compile anything if there are no scripts to compile.
|
|
if (!canStartIonCompile())
|
|
return false;
|
|
|
|
// Count the number of threads currently compiling scripts, and look for
|
|
// the thread with the lowest priority.
|
|
HelperThread* lowestPriorityThread = lowestPriorityUnpausedIonCompileAtThreshold();
|
|
|
|
// If the number of threads building scripts is less than the maximum, the
|
|
// compilation can start immediately.
|
|
if (!lowestPriorityThread)
|
|
return true;
|
|
|
|
// If there is a builder in the worklist with higher priority than some
|
|
// builder currently being compiled, then that current compilation can be
|
|
// paused, so allow the compilation.
|
|
if (IonBuilderHasHigherPriority(highestPriorityPendingIonCompile(),
|
|
lowestPriorityThread->ionBuilder()))
|
|
return true;
|
|
|
|
// Compilation will have to wait until one of the active compilations finishes.
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
GlobalHelperThreadState::canStartParseTask()
|
|
{
|
|
MOZ_ASSERT(isLocked());
|
|
return !parseWorklist().empty() && checkTaskThreadLimit<ParseTask*>(maxParseThreads());
|
|
}
|
|
|
|
bool
|
|
GlobalHelperThreadState::canStartCompressionTask()
|
|
{
|
|
return !compressionWorklist().empty() &&
|
|
checkTaskThreadLimit<SourceCompressionTask*>(maxCompressionThreads());
|
|
}
|
|
|
|
bool
|
|
GlobalHelperThreadState::canStartGCHelperTask()
|
|
{
|
|
return !gcHelperWorklist().empty() &&
|
|
checkTaskThreadLimit<GCHelperState*>(maxGCHelperThreads());
|
|
}
|
|
|
|
bool
|
|
GlobalHelperThreadState::canStartGCParallelTask()
|
|
{
|
|
return !gcParallelWorklist().empty() &&
|
|
checkTaskThreadLimit<GCParallelTask*>(maxGCParallelThreads());
|
|
}
|
|
|
|
js::GCParallelTask::~GCParallelTask()
|
|
{
|
|
// Only most-derived classes' destructors may do the join: base class
|
|
// destructors run after those for derived classes' members, so a join in a
|
|
// base class can't ensure that the task is done using the members. All we
|
|
// can do now is check that someone has previously stopped the task.
|
|
#ifdef DEBUG
|
|
AutoLockHelperThreadState helperLock;
|
|
MOZ_ASSERT(state == NotStarted);
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
js::GCParallelTask::startWithLockHeld()
|
|
{
|
|
MOZ_ASSERT(HelperThreadState().isLocked());
|
|
|
|
// Tasks cannot be started twice.
|
|
MOZ_ASSERT(state == NotStarted);
|
|
|
|
// If we do the shutdown GC before running anything, we may never
|
|
// have initialized the helper threads. Just use the serial path
|
|
// since we cannot safely intialize them at this point.
|
|
if (!HelperThreadState().threads)
|
|
return false;
|
|
|
|
if (!HelperThreadState().gcParallelWorklist().append(this))
|
|
return false;
|
|
state = Dispatched;
|
|
|
|
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
js::GCParallelTask::start()
|
|
{
|
|
AutoLockHelperThreadState helperLock;
|
|
return startWithLockHeld();
|
|
}
|
|
|
|
void
|
|
js::GCParallelTask::joinWithLockHeld()
|
|
{
|
|
MOZ_ASSERT(HelperThreadState().isLocked());
|
|
|
|
if (state == NotStarted)
|
|
return;
|
|
|
|
while (state != Finished)
|
|
HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
|
|
state = NotStarted;
|
|
cancel_ = false;
|
|
}
|
|
|
|
void
|
|
js::GCParallelTask::join()
|
|
{
|
|
AutoLockHelperThreadState helperLock;
|
|
joinWithLockHeld();
|
|
}
|
|
|
|
void
|
|
js::GCParallelTask::runFromMainThread(JSRuntime* rt)
|
|
{
|
|
MOZ_ASSERT(state == NotStarted);
|
|
MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(rt));
|
|
uint64_t timeStart = PRMJ_Now();
|
|
run();
|
|
duration_ = PRMJ_Now() - timeStart;
|
|
}
|
|
|
|
void
|
|
js::GCParallelTask::runFromHelperThread()
|
|
{
|
|
MOZ_ASSERT(HelperThreadState().isLocked());
|
|
|
|
{
|
|
AutoUnlockHelperThreadState parallelSection;
|
|
uint64_t timeStart = PRMJ_Now();
|
|
run();
|
|
duration_ = PRMJ_Now() - timeStart;
|
|
}
|
|
|
|
state = Finished;
|
|
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
|
|
}
|
|
|
|
bool
|
|
js::GCParallelTask::isRunning() const
|
|
{
|
|
MOZ_ASSERT(HelperThreadState().isLocked());
|
|
return state == Dispatched;
|
|
}
|
|
|
|
void
|
|
HelperThread::handleGCParallelWorkload()
|
|
{
|
|
MOZ_ASSERT(HelperThreadState().isLocked());
|
|
MOZ_ASSERT(HelperThreadState().canStartGCParallelTask());
|
|
MOZ_ASSERT(idle());
|
|
|
|
currentTask.emplace(HelperThreadState().gcParallelWorklist().popCopy());
|
|
gcParallelTask()->runFromHelperThread();
|
|
currentTask.reset();
|
|
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
|
|
}
|
|
|
|
static void
|
|
LeaveParseTaskZone(JSRuntime* rt, ParseTask* task)
|
|
{
|
|
// Mark the zone as no longer in use by an ExclusiveContext, and available
|
|
// to be collected by the GC.
|
|
task->cx->leaveCompartment(task->cx->compartment());
|
|
rt->clearUsedByExclusiveThread(task->cx->zone());
|
|
}
|
|
|
|
JSScript*
|
|
GlobalHelperThreadState::finishParseTask(JSContext* maybecx, JSRuntime* rt, void* token)
|
|
{
|
|
ScopedJSDeletePtr<ParseTask> parseTask;
|
|
|
|
// The token is a ParseTask* which should be in the finished list.
|
|
// Find and remove its entry.
|
|
{
|
|
AutoLockHelperThreadState lock;
|
|
ParseTaskVector& finished = parseFinishedList();
|
|
for (size_t i = 0; i < finished.length(); i++) {
|
|
if (finished[i] == token) {
|
|
parseTask = finished[i];
|
|
remove(finished, &i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
MOZ_ASSERT(parseTask);
|
|
|
|
if (!maybecx) {
|
|
LeaveParseTaskZone(rt, parseTask);
|
|
return nullptr;
|
|
}
|
|
|
|
JSContext* cx = maybecx;
|
|
MOZ_ASSERT(cx->compartment());
|
|
|
|
// Make sure we have all the constructors we need for the prototype
|
|
// remapping below, since we can't GC while that's happening.
|
|
Rooted<GlobalObject*> global(cx, &cx->global()->as<GlobalObject>());
|
|
if (!EnsureParserCreatedClasses(cx)) {
|
|
LeaveParseTaskZone(rt, parseTask);
|
|
return nullptr;
|
|
}
|
|
|
|
mergeParseTaskCompartment(rt, parseTask, global, cx->compartment());
|
|
|
|
RootedScript script(rt, parseTask->script);
|
|
releaseAssertSameCompartment(cx, script);
|
|
|
|
if (!parseTask->finish(cx))
|
|
return nullptr;
|
|
|
|
// Report any error or warnings generated during the parse, and inform the
|
|
// debugger about the compiled scripts.
|
|
for (size_t i = 0; i < parseTask->errors.length(); i++)
|
|
parseTask->errors[i]->throwError(cx);
|
|
if (parseTask->overRecursed)
|
|
ReportOverRecursed(cx);
|
|
if (cx->isExceptionPending())
|
|
return nullptr;
|
|
|
|
if (!script) {
|
|
// No error was reported, but no script produced. Assume we hit out of
|
|
// memory.
|
|
ReportOutOfMemory(cx);
|
|
return nullptr;
|
|
}
|
|
|
|
// The Debugger only needs to be told about the topmost script that was compiled.
|
|
Debugger::onNewScript(cx, script);
|
|
|
|
// Update the compressed source table with the result. This is normally
|
|
// called by setCompressedSource when compilation occurs on the main thread.
|
|
if (script->scriptSource()->hasCompressedSource())
|
|
script->scriptSource()->updateCompressedSourceSet(rt);
|
|
|
|
return script;
|
|
}
|
|
|
|
JSObject*
|
|
GlobalObject::getStarGeneratorFunctionPrototype()
|
|
{
|
|
const Value& v = getReservedSlot(STAR_GENERATOR_FUNCTION_PROTO);
|
|
return v.isObject() ? &v.toObject() : nullptr;
|
|
}
|
|
|
|
void
|
|
GlobalHelperThreadState::mergeParseTaskCompartment(JSRuntime* rt, ParseTask* parseTask,
|
|
Handle<GlobalObject*> global,
|
|
JSCompartment* dest)
|
|
{
|
|
// After we call LeaveParseTaskZone() it's not safe to GC until we have
|
|
// finished merging the contents of the parse task's compartment into the
|
|
// destination compartment. Finish any ongoing incremental GC first and
|
|
// assert that no allocation can occur. (Bug 1371908 changes this, but
|
|
// generates assertion failures.)
|
|
gc::AutoFinishGC finishGC(rt);
|
|
JS::AutoAssertNoAlloc noAlloc(rt);
|
|
|
|
LeaveParseTaskZone(rt, parseTask);
|
|
|
|
{
|
|
gc::ZoneCellIter iter(parseTask->cx->zone(), gc::AllocKind::OBJECT_GROUP);
|
|
|
|
// Generator functions don't have Function.prototype as prototype but a
|
|
// different function object, so the IdentifyStandardPrototype trick
|
|
// below won't work. Just special-case it.
|
|
JSObject* parseTaskStarGenFunctionProto =
|
|
parseTask->exclusiveContextGlobal->as<GlobalObject>().getStarGeneratorFunctionPrototype();
|
|
|
|
// Point the prototypes of any objects in the script's compartment to refer
|
|
// to the corresponding prototype in the new compartment. This will briefly
|
|
// create cross compartment pointers, which will be fixed by the
|
|
// MergeCompartments call below.
|
|
for (; !iter.done(); iter.next()) {
|
|
ObjectGroup* group = iter.get<ObjectGroup>();
|
|
TaggedProto proto(group->proto());
|
|
if (!proto.isObject())
|
|
continue;
|
|
|
|
JSObject* protoObj = proto.toObject();
|
|
|
|
JSObject* newProto;
|
|
if (protoObj == parseTaskStarGenFunctionProto) {
|
|
newProto = global->getStarGeneratorFunctionPrototype();
|
|
} else {
|
|
JSProtoKey key = JS::IdentifyStandardPrototype(protoObj);
|
|
if (key == JSProto_Null)
|
|
continue;
|
|
|
|
MOZ_ASSERT(key == JSProto_Object || key == JSProto_Array ||
|
|
key == JSProto_Function || key == JSProto_RegExp ||
|
|
key == JSProto_Iterator);
|
|
|
|
newProto = GetBuiltinPrototypePure(global, key);
|
|
}
|
|
|
|
MOZ_ASSERT(newProto);
|
|
group->setProtoUnchecked(TaggedProto(newProto));
|
|
}
|
|
}
|
|
|
|
// Move the parsed script and all its contents into the desired compartment.
|
|
gc::MergeCompartments(parseTask->cx->compartment(), dest);
|
|
}
|
|
|
|
void
|
|
HelperThread::destroy()
|
|
{
|
|
if (thread) {
|
|
{
|
|
AutoLockHelperThreadState lock;
|
|
terminate = true;
|
|
|
|
/* Notify all helpers, to ensure that this thread wakes up. */
|
|
HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER);
|
|
}
|
|
|
|
PR_JoinThread(thread);
|
|
}
|
|
|
|
threadData.reset();
|
|
}
|
|
|
|
#ifdef MOZ_NUWA_PROCESS
|
|
extern "C" {
|
|
MFBT_API bool IsNuwaProcess();
|
|
MFBT_API void NuwaMarkCurrentThread(void (*recreate)(void*), void* arg);
|
|
}
|
|
#endif
|
|
|
|
/* static */
|
|
void
|
|
HelperThread::ThreadMain(void* arg)
|
|
{
|
|
PR_SetCurrentThreadName("JS Helper");
|
|
|
|
#ifdef MOZ_NUWA_PROCESS
|
|
if (IsNuwaProcess()) {
|
|
MOZ_ASSERT(NuwaMarkCurrentThread != nullptr);
|
|
NuwaMarkCurrentThread(nullptr, nullptr);
|
|
}
|
|
#endif
|
|
|
|
//See bug 1104658.
|
|
//Set the FPU control word to be the same as the main thread's, or math
|
|
//computations on this thread may use incorrect precision rules during
|
|
//Ion compilation.
|
|
FIX_FPU();
|
|
|
|
static_cast<HelperThread*>(arg)->threadLoop();
|
|
}
|
|
|
|
void
|
|
HelperThread::handleWasmWorkload()
|
|
{
|
|
MOZ_ASSERT(HelperThreadState().isLocked());
|
|
MOZ_ASSERT(HelperThreadState().canStartWasmCompile());
|
|
MOZ_ASSERT(idle());
|
|
|
|
currentTask.emplace(HelperThreadState().wasmWorklist().popCopy());
|
|
bool success = false;
|
|
|
|
wasm::CompileTask* task = wasmTask();
|
|
{
|
|
AutoUnlockHelperThreadState unlock;
|
|
PerThreadData::AutoEnterRuntime enter(threadData.ptr(), task->args().runtime);
|
|
success = wasm::CompileFunction(task);
|
|
}
|
|
|
|
// On success, try to move work to the finished list.
|
|
if (success)
|
|
success = HelperThreadState().wasmFinishedList().append(task);
|
|
|
|
// On failure, note the failure for harvesting by the parent.
|
|
if (!success)
|
|
HelperThreadState().noteWasmFailure();
|
|
|
|
// Notify the main thread in case it's waiting.
|
|
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
|
|
currentTask.reset();
|
|
}
|
|
|
|
void
|
|
HelperThread::handleIonWorkload()
|
|
{
|
|
MOZ_ASSERT(HelperThreadState().isLocked());
|
|
MOZ_ASSERT(HelperThreadState().canStartIonCompile());
|
|
MOZ_ASSERT(idle());
|
|
|
|
// Find the IonBuilder in the worklist with the highest priority, and
|
|
// remove it from the worklist.
|
|
jit::IonBuilder* builder =
|
|
HelperThreadState().highestPriorityPendingIonCompile(/* remove = */ true);
|
|
|
|
// If there are now too many threads with active IonBuilders, indicate to
|
|
// the one with the lowest priority that it should pause. Note that due to
|
|
// builder priorities changing since pendingIonCompileHasSufficientPriority
|
|
// was called, the builder we are pausing may actually be higher priority
|
|
// than the one we are about to start. Oh well.
|
|
if (HelperThread* other = HelperThreadState().lowestPriorityUnpausedIonCompileAtThreshold()) {
|
|
MOZ_ASSERT(other->ionBuilder() && !other->pause);
|
|
other->pause = true;
|
|
}
|
|
|
|
currentTask.emplace(builder);
|
|
builder->setPauseFlag(&pause);
|
|
|
|
TraceLoggerThread* logger = TraceLoggerForCurrentThread();
|
|
TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, builder->script());
|
|
AutoTraceLog logScript(logger, event);
|
|
AutoTraceLog logCompile(logger, TraceLogger_IonCompilation);
|
|
|
|
JSRuntime* rt = builder->script()->compartment()->runtimeFromAnyThread();
|
|
|
|
{
|
|
AutoUnlockHelperThreadState unlock;
|
|
PerThreadData::AutoEnterRuntime enter(threadData.ptr(),
|
|
builder->script()->runtimeFromAnyThread());
|
|
jit::JitContext jctx(jit::CompileRuntime::get(rt),
|
|
jit::CompileCompartment::get(builder->script()->compartment()),
|
|
&builder->alloc());
|
|
builder->setBackgroundCodegen(jit::CompileBackEnd(builder));
|
|
}
|
|
|
|
FinishOffThreadIonCompile(builder);
|
|
currentTask.reset();
|
|
pause = false;
|
|
|
|
// Ping the main thread so that the compiled code can be incorporated
|
|
// at the next interrupt callback. Don't interrupt Ion code for this, as
|
|
// this incorporation can be delayed indefinitely without affecting
|
|
// performance as long as the main thread is actually executing Ion code.
|
|
rt->requestInterrupt(JSRuntime::RequestInterruptCanWait);
|
|
|
|
// Notify the main thread in case it is waiting for the compilation to finish.
|
|
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
|
|
|
|
// When finishing Ion compilation jobs, we can start unpausing compilation
|
|
// threads that were paused to restrict the number of active compilations.
|
|
// Only unpause one at a time, to make sure we don't exceed the restriction.
|
|
// Since threads are currently only paused for Ion compilations, this
|
|
// strategy will eventually unpause all paused threads, regardless of how
|
|
// many there are, since each thread we unpause will eventually finish and
|
|
// end up back here.
|
|
if (HelperThread* other = HelperThreadState().highestPriorityPausedIonCompile()) {
|
|
MOZ_ASSERT(other->ionBuilder() && other->pause);
|
|
|
|
// Only unpause the other thread if there isn't a higher priority
|
|
// builder which this thread or another can start on.
|
|
jit::IonBuilder* builder = HelperThreadState().highestPriorityPendingIonCompile();
|
|
if (!builder || IonBuilderHasHigherPriority(other->ionBuilder(), builder)) {
|
|
other->pause = false;
|
|
|
|
// Notify all paused threads, to make sure the one we just
|
|
// unpaused wakes up.
|
|
HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
static HelperThread*
|
|
CurrentHelperThread()
|
|
{
|
|
PRThread* prThread = PR_GetCurrentThread();
|
|
HelperThread* thread = nullptr;
|
|
for (size_t i = 0; i < HelperThreadState().threadCount; i++) {
|
|
if (prThread == HelperThreadState().threads[i].thread) {
|
|
thread = &HelperThreadState().threads[i];
|
|
break;
|
|
}
|
|
}
|
|
MOZ_ASSERT(thread);
|
|
return thread;
|
|
}
|
|
|
|
void
|
|
js::PauseCurrentHelperThread()
|
|
{
|
|
TraceLoggerThread* logger = TraceLoggerForCurrentThread();
|
|
AutoTraceLog logPaused(logger, TraceLogger_IonCompilationPaused);
|
|
|
|
HelperThread* thread = CurrentHelperThread();
|
|
|
|
AutoLockHelperThreadState lock;
|
|
while (thread->pause)
|
|
HelperThreadState().wait(GlobalHelperThreadState::PAUSE);
|
|
}
|
|
|
|
void
|
|
ExclusiveContext::setHelperThread(HelperThread* thread)
|
|
{
|
|
helperThread_ = thread;
|
|
perThreadData = thread->threadData.ptr();
|
|
}
|
|
|
|
frontend::CompileError&
|
|
ExclusiveContext::addPendingCompileError()
|
|
{
|
|
frontend::CompileError* error = js_new<frontend::CompileError>();
|
|
if (!error)
|
|
MOZ_CRASH();
|
|
if (!helperThread()->parseTask()->errors.append(error))
|
|
MOZ_CRASH();
|
|
return *error;
|
|
}
|
|
|
|
void
|
|
ExclusiveContext::addPendingOverRecursed()
|
|
{
|
|
if (helperThread()->parseTask())
|
|
helperThread()->parseTask()->overRecursed = true;
|
|
}
|
|
|
|
void
|
|
HelperThread::handleParseWorkload()
|
|
{
|
|
MOZ_ASSERT(HelperThreadState().isLocked());
|
|
MOZ_ASSERT(HelperThreadState().canStartParseTask());
|
|
MOZ_ASSERT(idle());
|
|
|
|
currentTask.emplace(HelperThreadState().parseWorklist().popCopy());
|
|
ParseTask* task = parseTask();
|
|
task->cx->setHelperThread(this);
|
|
|
|
{
|
|
AutoUnlockHelperThreadState unlock;
|
|
PerThreadData::AutoEnterRuntime enter(threadData.ptr(),
|
|
task->exclusiveContextGlobal->runtimeFromAnyThread());
|
|
SourceBufferHolder srcBuf(task->chars, task->length,
|
|
SourceBufferHolder::NoOwnership);
|
|
|
|
// ! WARNING WARNING WARNING !
|
|
//
|
|
// See comment in Parser::bindLexical about optimizing global lexical
|
|
// bindings. If we start optimizing them, passing in task->cx's
|
|
// global lexical scope would be incorrect!
|
|
//
|
|
// ! WARNING WARNING WARNING !
|
|
ExclusiveContext* parseCx = task->cx;
|
|
Rooted<ClonedBlockObject*> globalLexical(parseCx, &parseCx->global()->lexicalScope());
|
|
Rooted<ScopeObject*> staticScope(parseCx, &globalLexical->staticBlock());
|
|
task->script = frontend::CompileScript(parseCx, &task->alloc,
|
|
globalLexical, staticScope, nullptr,
|
|
task->options, srcBuf,
|
|
/* source_ = */ nullptr,
|
|
/* extraSct = */ nullptr,
|
|
/* sourceObjectOut = */ &task->sourceObject);
|
|
}
|
|
|
|
// The callback is invoked while we are still off the main thread.
|
|
task->callback(task, task->callbackData);
|
|
|
|
// FinishOffThreadScript will need to be called on the script to
|
|
// migrate it into the correct compartment.
|
|
{
|
|
AutoEnterOOMUnsafeRegion oomUnsafe;
|
|
if (!HelperThreadState().parseFinishedList().append(task))
|
|
oomUnsafe.crash("handleParseWorkload");
|
|
}
|
|
|
|
currentTask.reset();
|
|
|
|
// Notify the main thread in case it is waiting for the parse/emit to finish.
|
|
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
|
|
}
|
|
|
|
void
|
|
HelperThread::handleCompressionWorkload()
|
|
{
|
|
MOZ_ASSERT(HelperThreadState().isLocked());
|
|
MOZ_ASSERT(HelperThreadState().canStartCompressionTask());
|
|
MOZ_ASSERT(idle());
|
|
|
|
currentTask.emplace(HelperThreadState().compressionWorklist().popCopy());
|
|
SourceCompressionTask* task = compressionTask();
|
|
task->helperThread = this;
|
|
|
|
{
|
|
AutoUnlockHelperThreadState unlock;
|
|
task->result = task->work();
|
|
}
|
|
|
|
task->helperThread = nullptr;
|
|
currentTask.reset();
|
|
|
|
// Notify the main thread in case it is waiting for the compression to finish.
|
|
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
|
|
}
|
|
|
|
bool
|
|
js::StartOffThreadCompression(ExclusiveContext* cx, SourceCompressionTask* task)
|
|
{
|
|
AutoLockHelperThreadState lock;
|
|
|
|
if (!HelperThreadState().compressionWorklist().append(task)) {
|
|
if (JSContext* maybecx = cx->maybeJSContext())
|
|
ReportOutOfMemory(maybecx);
|
|
return false;
|
|
}
|
|
|
|
HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
GlobalHelperThreadState::compressionInProgress(SourceCompressionTask* task)
|
|
{
|
|
MOZ_ASSERT(isLocked());
|
|
for (size_t i = 0; i < compressionWorklist().length(); i++) {
|
|
if (compressionWorklist()[i] == task)
|
|
return true;
|
|
}
|
|
for (size_t i = 0; i < threadCount; i++) {
|
|
if (threads[i].compressionTask() == task)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
SourceCompressionTask::complete()
|
|
{
|
|
if (!active()) {
|
|
MOZ_ASSERT(!compressed);
|
|
return true;
|
|
}
|
|
|
|
{
|
|
AutoLockHelperThreadState lock;
|
|
while (HelperThreadState().compressionInProgress(this))
|
|
HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
|
|
}
|
|
|
|
if (result == Success) {
|
|
ss->setCompressedSource(cx->isJSContext() ? cx->asJSContext()->runtime() : nullptr,
|
|
compressed, compressedBytes, compressedHash);
|
|
|
|
// Update memory accounting.
|
|
cx->updateMallocCounter(ss->computedSizeOfData());
|
|
} else {
|
|
js_free(compressed);
|
|
|
|
if (result == OOM)
|
|
ReportOutOfMemory(cx);
|
|
else if (result == Aborted && !ss->ensureOwnsSource(cx))
|
|
result = OOM;
|
|
}
|
|
|
|
ss = nullptr;
|
|
compressed = nullptr;
|
|
MOZ_ASSERT(!active());
|
|
|
|
return result != OOM;
|
|
}
|
|
|
|
SourceCompressionTask*
|
|
GlobalHelperThreadState::compressionTaskForSource(ScriptSource* ss)
|
|
{
|
|
MOZ_ASSERT(isLocked());
|
|
for (size_t i = 0; i < compressionWorklist().length(); i++) {
|
|
SourceCompressionTask* task = compressionWorklist()[i];
|
|
if (task->source() == ss)
|
|
return task;
|
|
}
|
|
for (size_t i = 0; i < threadCount; i++) {
|
|
SourceCompressionTask* task = threads[i].compressionTask();
|
|
if (task && task->source() == ss)
|
|
return task;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
GlobalHelperThreadState::trace(JSTracer* trc)
|
|
{
|
|
AutoLockHelperThreadState lock;
|
|
for (auto builder : ionWorklist())
|
|
builder->trace(trc);
|
|
for (auto builder : ionFinishedList())
|
|
builder->trace(trc);
|
|
|
|
if (HelperThreadState().threads) {
|
|
for (size_t i = 0; i < HelperThreadState().threadCount; i++) {
|
|
HelperThread& helper = HelperThreadState().threads[i];
|
|
if (auto builder = helper.ionBuilder())
|
|
builder->trace(trc);
|
|
}
|
|
}
|
|
|
|
jit::IonBuilder* builder = HelperThreadState().ionLazyLinkList().getFirst();
|
|
while (builder) {
|
|
builder->trace(trc);
|
|
builder = builder->getNext();
|
|
}
|
|
|
|
for (auto parseTask : parseWorklist_)
|
|
parseTask->trace(trc);
|
|
for (auto parseTask : parseFinishedList_)
|
|
parseTask->trace(trc);
|
|
for (auto parseTask : parseWaitingOnGC_)
|
|
parseTask->trace(trc);
|
|
}
|
|
|
|
void
|
|
HelperThread::handleGCHelperWorkload()
|
|
{
|
|
MOZ_ASSERT(HelperThreadState().isLocked());
|
|
MOZ_ASSERT(HelperThreadState().canStartGCHelperTask());
|
|
MOZ_ASSERT(idle());
|
|
|
|
currentTask.emplace(HelperThreadState().gcHelperWorklist().popCopy());
|
|
GCHelperState* task = gcHelperTask();
|
|
|
|
{
|
|
AutoUnlockHelperThreadState unlock;
|
|
task->work();
|
|
}
|
|
|
|
currentTask.reset();
|
|
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
|
|
}
|
|
|
|
void
|
|
HelperThread::threadLoop()
|
|
{
|
|
MOZ_ASSERT(CanUseExtraThreads());
|
|
|
|
JS::AutoSuppressGCAnalysis nogc;
|
|
AutoLockHelperThreadState lock;
|
|
|
|
js::TlsPerThreadData.set(threadData.ptr());
|
|
|
|
// Compute the thread's stack limit, for over-recursed checks.
|
|
uintptr_t stackLimit = GetNativeStackBase();
|
|
#if JS_STACK_GROWTH_DIRECTION > 0
|
|
stackLimit += HELPER_STACK_QUOTA;
|
|
#else
|
|
stackLimit -= HELPER_STACK_QUOTA;
|
|
#endif
|
|
for (size_t i = 0; i < ArrayLength(threadData->nativeStackLimit); i++)
|
|
threadData->nativeStackLimit[i] = stackLimit;
|
|
|
|
while (true) {
|
|
MOZ_ASSERT(idle());
|
|
|
|
// 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().canStartWasmCompile() ||
|
|
(ionCompile = HelperThreadState().pendingIonCompileHasSufficientPriority()) ||
|
|
HelperThreadState().canStartParseTask() ||
|
|
HelperThreadState().canStartCompressionTask() ||
|
|
HelperThreadState().canStartGCHelperTask() ||
|
|
HelperThreadState().canStartGCParallelTask())
|
|
{
|
|
break;
|
|
}
|
|
HelperThreadState().wait(GlobalHelperThreadState::PRODUCER);
|
|
}
|
|
|
|
// Dispatch tasks, prioritizing wasm work.
|
|
if (HelperThreadState().canStartWasmCompile()) {
|
|
js::oom::SetThreadType(js::oom::THREAD_TYPE_ASMJS);
|
|
handleWasmWorkload();
|
|
} else if (ionCompile) {
|
|
js::oom::SetThreadType(js::oom::THREAD_TYPE_ION);
|
|
handleIonWorkload();
|
|
} else if (HelperThreadState().canStartParseTask()) {
|
|
js::oom::SetThreadType(js::oom::THREAD_TYPE_PARSE);
|
|
handleParseWorkload();
|
|
} else if (HelperThreadState().canStartCompressionTask()) {
|
|
js::oom::SetThreadType(js::oom::THREAD_TYPE_COMPRESS);
|
|
handleCompressionWorkload();
|
|
} else if (HelperThreadState().canStartGCHelperTask()) {
|
|
js::oom::SetThreadType(js::oom::THREAD_TYPE_GCHELPER);
|
|
handleGCHelperWorkload();
|
|
} else if (HelperThreadState().canStartGCParallelTask()) {
|
|
js::oom::SetThreadType(js::oom::THREAD_TYPE_GCPARALLEL);
|
|
handleGCParallelWorkload();
|
|
} else {
|
|
MOZ_CRASH("No task to perform");
|
|
}
|
|
}
|
|
}
|