945 lines
26 KiB
C++
945 lines
26 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 "mozilla/ArrayUtils.h"
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/MemoryReporting.h"
|
|
#include "mozilla/ThreadLocal.h"
|
|
#include "mozilla/Unused.h"
|
|
|
|
#if defined(XP_DARWIN)
|
|
#include <mach/mach.h>
|
|
#elif defined(XP_UNIX)
|
|
#include <sys/resource.h>
|
|
#elif defined(XP_WIN)
|
|
#include <processthreadsapi.h>
|
|
#include <windows.h>
|
|
#endif // defined(XP_DARWIN) || defined(XP_UNIX) || defined(XP_WIN)
|
|
|
|
#include <locale.h>
|
|
#include <string.h>
|
|
|
|
#ifdef JS_CAN_CHECK_THREADSAFE_ACCESSES
|
|
# include <sys/mman.h>
|
|
#endif
|
|
|
|
#include "jsatom.h"
|
|
#include "jsdtoa.h"
|
|
#include "jsgc.h"
|
|
#include "jsmath.h"
|
|
#include "jsnativestack.h"
|
|
#include "jsobj.h"
|
|
#include "jsscript.h"
|
|
#include "jswin.h"
|
|
#include "jswrapper.h"
|
|
|
|
#include "builtin/Promise.h"
|
|
#include "gc/GCInternals.h"
|
|
#include "jit/arm/Simulator-arm.h"
|
|
#include "jit/arm64/vixl/Simulator-vixl.h"
|
|
#include "jit/IonBuilder.h"
|
|
#include "jit/JitCompartment.h"
|
|
#include "jit/mips32/Simulator-mips32.h"
|
|
#include "jit/mips64/Simulator-mips64.h"
|
|
#include "jit/PcScriptCache.h"
|
|
#include "js/Date.h"
|
|
#include "js/MemoryMetrics.h"
|
|
#include "js/SliceBudget.h"
|
|
#include "vm/Debugger.h"
|
|
#include "wasm/WasmSignalHandlers.h"
|
|
|
|
#include "jscntxtinlines.h"
|
|
#include "jsgcinlines.h"
|
|
|
|
using namespace js;
|
|
using namespace js::gc;
|
|
|
|
using mozilla::Atomic;
|
|
using mozilla::DebugOnly;
|
|
using mozilla::NegativeInfinity;
|
|
using mozilla::PodZero;
|
|
using mozilla::PodArrayZero;
|
|
using mozilla::PositiveInfinity;
|
|
using JS::GenericNaN;
|
|
using JS::DoubleNaNValue;
|
|
|
|
/* static */ MOZ_THREAD_LOCAL(PerThreadData*) js::TlsPerThreadData;
|
|
/* static */ Atomic<size_t> JSRuntime::liveRuntimesCount;
|
|
|
|
namespace js {
|
|
bool gCanUseExtraThreads = true;
|
|
} // namespace js
|
|
|
|
void
|
|
js::DisableExtraThreads()
|
|
{
|
|
gCanUseExtraThreads = false;
|
|
}
|
|
|
|
const JSSecurityCallbacks js::NullSecurityCallbacks = { };
|
|
|
|
PerThreadData::PerThreadData(JSRuntime* runtime)
|
|
: runtime_(runtime)
|
|
#ifdef JS_TRACE_LOGGING
|
|
, traceLogger(nullptr)
|
|
#endif
|
|
, autoFlushICache_(nullptr)
|
|
, dtoaState(nullptr)
|
|
, suppressGC(0)
|
|
#ifdef DEBUG
|
|
, ionCompiling(false)
|
|
, ionCompilingSafeForMinorGC(false)
|
|
, performingGC(false)
|
|
, gcSweeping(false)
|
|
#endif
|
|
{}
|
|
|
|
PerThreadData::~PerThreadData()
|
|
{
|
|
if (dtoaState)
|
|
DestroyDtoaState(dtoaState);
|
|
}
|
|
|
|
bool
|
|
PerThreadData::init()
|
|
{
|
|
dtoaState = NewDtoaState();
|
|
if (!dtoaState)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static const JSWrapObjectCallbacks DefaultWrapObjectCallbacks = {
|
|
TransparentObjectWrapper,
|
|
nullptr
|
|
};
|
|
|
|
static size_t
|
|
ReturnZeroSize(const void* p)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
JSRuntime::JSRuntime(JSRuntime* parentRuntime)
|
|
: mainThread(this),
|
|
jitTop(nullptr),
|
|
jitActivation(nullptr),
|
|
jitStackLimit_(0xbad),
|
|
jitStackLimitNoInterrupt_(0xbad),
|
|
#ifdef DEBUG
|
|
ionBailAfter_(0),
|
|
#endif
|
|
activation_(nullptr),
|
|
profilingActivation_(nullptr),
|
|
profilerSampleBufferGen_(0),
|
|
profilerSampleBufferLapCount_(1),
|
|
wasmActivationStack_(nullptr),
|
|
entryMonitor(nullptr),
|
|
noExecuteDebuggerTop(nullptr),
|
|
parentRuntime(parentRuntime),
|
|
#ifdef DEBUG
|
|
updateChildRuntimeCount(parentRuntime),
|
|
#endif
|
|
interrupt_(false),
|
|
handlingSegFault(false),
|
|
handlingJitInterrupt_(false),
|
|
interruptCallbackDisabled(false),
|
|
getIncumbentGlobalCallback(nullptr),
|
|
enqueuePromiseJobCallback(nullptr),
|
|
enqueuePromiseJobCallbackData(nullptr),
|
|
promiseRejectionTrackerCallback(nullptr),
|
|
promiseRejectionTrackerCallbackData(nullptr),
|
|
startAsyncTaskCallback(nullptr),
|
|
finishAsyncTaskCallback(nullptr),
|
|
promiseTasksToDestroy(mutexid::PromiseTaskPtrVector),
|
|
exclusiveAccessLock(mutexid::RuntimeExclusiveAccess),
|
|
#ifdef DEBUG
|
|
mainThreadHasExclusiveAccess(false),
|
|
#endif
|
|
numExclusiveThreads(0),
|
|
numCompartments(0),
|
|
localeCallbacks(nullptr),
|
|
defaultLocale(nullptr),
|
|
defaultVersion_(JSVERSION_DEFAULT),
|
|
ownerThread_(js::ThisThread::GetId()),
|
|
ownerThreadNative_(0),
|
|
tempLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
|
|
jitRuntime_(nullptr),
|
|
selfHostingGlobal_(nullptr),
|
|
nativeStackBase(GetNativeStackBase()),
|
|
destroyCompartmentCallback(nullptr),
|
|
sizeOfIncludingThisCompartmentCallback(nullptr),
|
|
destroyZoneCallback(nullptr),
|
|
sweepZoneCallback(nullptr),
|
|
compartmentNameCallback(nullptr),
|
|
activityCallback(nullptr),
|
|
activityCallbackArg(nullptr),
|
|
requestDepth(0),
|
|
#ifdef DEBUG
|
|
checkRequestDepth(0),
|
|
#endif
|
|
gc(thisFromCtor()),
|
|
gcInitialized(false),
|
|
#ifdef JS_SIMULATOR
|
|
simulator_(nullptr),
|
|
#endif
|
|
scriptAndCountsVector(nullptr),
|
|
lcovOutput(),
|
|
NaNValue(DoubleNaNValue()),
|
|
negativeInfinityValue(DoubleValue(NegativeInfinity<double>())),
|
|
positiveInfinityValue(DoubleValue(PositiveInfinity<double>())),
|
|
emptyString(nullptr),
|
|
spsProfiler(thisFromCtor()),
|
|
profilingScripts(false),
|
|
suppressProfilerSampling(false),
|
|
hadOutOfMemory(false),
|
|
#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
|
|
runningOOMTest(false),
|
|
#endif
|
|
allowRelazificationForTesting(false),
|
|
defaultFreeOp_(nullptr),
|
|
debuggerMutations(0),
|
|
securityCallbacks(&NullSecurityCallbacks),
|
|
DOMcallbacks(nullptr),
|
|
destroyPrincipals(nullptr),
|
|
readPrincipals(nullptr),
|
|
warningReporter(nullptr),
|
|
buildIdOp(nullptr),
|
|
propertyRemovals(0),
|
|
keepAtoms_(0),
|
|
trustedPrincipals_(nullptr),
|
|
beingDestroyed_(false),
|
|
atoms_(nullptr),
|
|
atomsCompartment_(nullptr),
|
|
staticStrings(nullptr),
|
|
commonNames(nullptr),
|
|
permanentAtoms(nullptr),
|
|
wellKnownSymbols(nullptr),
|
|
wrapObjectCallbacks(&DefaultWrapObjectCallbacks),
|
|
preserveWrapperCallback(nullptr),
|
|
jitSupportsFloatingPoint(false),
|
|
jitSupportsUnalignedAccesses(false),
|
|
jitSupportsSimd(false),
|
|
ionPcScriptCache(nullptr),
|
|
scriptEnvironmentPreparer(nullptr),
|
|
ctypesActivityCallback(nullptr),
|
|
windowProxyClass_(nullptr),
|
|
offthreadIonCompilationEnabled_(true),
|
|
parallelParsingEnabled_(true),
|
|
autoWritableJitCodeActive_(false),
|
|
#ifdef DEBUG
|
|
enteredPolicy(nullptr),
|
|
#endif
|
|
largeAllocationFailureCallback(nullptr),
|
|
oomCallback(nullptr),
|
|
debuggerMallocSizeOf(ReturnZeroSize),
|
|
lastAnimationTime(0),
|
|
performanceMonitoring(thisFromCtor()),
|
|
ionLazyLinkListSize_(0),
|
|
stackFormat_(parentRuntime ?
|
|
js::StackFormat::Default :
|
|
js::StackFormat::SpiderMonkey),
|
|
moduleResolveHook()
|
|
{
|
|
setGCStoreBufferPtr(&gc.storeBuffer);
|
|
|
|
liveRuntimesCount++;
|
|
|
|
/* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
|
|
JS_INIT_CLIST(&onNewGlobalObjectWatchers);
|
|
|
|
PodArrayZero(nativeStackQuota);
|
|
PodZero(&asmJSCacheOps);
|
|
lcovOutput.init();
|
|
}
|
|
|
|
bool
|
|
JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes)
|
|
{
|
|
MOZ_ASSERT(ownerThread_ == js::ThisThread::GetId());
|
|
|
|
// Get a platform-native handle for the owner thread, used by
|
|
// js::InterruptRunningJitCode to halt the runtime's main thread.
|
|
#ifdef XP_WIN
|
|
size_t openFlags = THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME |
|
|
THREAD_QUERY_INFORMATION;
|
|
HANDLE self = OpenThread(openFlags, false, GetCurrentThreadId());
|
|
if (!self)
|
|
return false;
|
|
static_assert(sizeof(HANDLE) <= sizeof(ownerThreadNative_), "need bigger field");
|
|
ownerThreadNative_ = (size_t)self;
|
|
#else
|
|
static_assert(sizeof(pthread_t) <= sizeof(ownerThreadNative_), "need bigger field");
|
|
ownerThreadNative_ = (size_t)pthread_self();
|
|
#endif
|
|
|
|
if (!mainThread.init())
|
|
return false;
|
|
|
|
if (!regexpStack.init())
|
|
return false;
|
|
|
|
if (CanUseExtraThreads() && !EnsureHelperThreadsInitialized())
|
|
return false;
|
|
|
|
js::TlsPerThreadData.set(&mainThread);
|
|
|
|
defaultFreeOp_ = js_new<js::FreeOp>(this);
|
|
if (!defaultFreeOp_)
|
|
return false;
|
|
|
|
if (!gc.init(maxbytes, maxNurseryBytes))
|
|
return false;
|
|
|
|
ScopedJSDeletePtr<Zone> atomsZone(new_<Zone>(this));
|
|
if (!atomsZone || !atomsZone->init(true))
|
|
return false;
|
|
|
|
JS::CompartmentOptions options;
|
|
ScopedJSDeletePtr<JSCompartment> atomsCompartment(new_<JSCompartment>(atomsZone.get(), options));
|
|
if (!atomsCompartment || !atomsCompartment->init(nullptr))
|
|
return false;
|
|
|
|
if (!gc.zones.append(atomsZone.get()))
|
|
return false;
|
|
if (!atomsZone->compartments.append(atomsCompartment.get()))
|
|
return false;
|
|
|
|
atomsCompartment->setIsSystem(true);
|
|
atomsCompartment->setIsAtomsCompartment();
|
|
|
|
atomsZone.forget();
|
|
this->atomsCompartment_ = atomsCompartment.forget();
|
|
|
|
if (!symbolRegistry_.init())
|
|
return false;
|
|
|
|
if (!scriptDataTable_.init())
|
|
return false;
|
|
|
|
/* The garbage collector depends on everything before this point being initialized. */
|
|
gcInitialized = true;
|
|
|
|
InitRuntimeNumberState(this);
|
|
|
|
JS::ResetTimeZone();
|
|
|
|
#ifdef JS_SIMULATOR
|
|
simulator_ = js::jit::Simulator::Create(contextFromMainThread());
|
|
if (!simulator_)
|
|
return false;
|
|
#endif
|
|
|
|
jitSupportsFloatingPoint = js::jit::JitSupportsFloatingPoint();
|
|
jitSupportsUnalignedAccesses = js::jit::JitSupportsUnalignedAccesses();
|
|
jitSupportsSimd = js::jit::JitSupportsSimd();
|
|
|
|
if (!wasm::EnsureSignalHandlers(this))
|
|
return false;
|
|
|
|
if (!spsProfiler.init())
|
|
return false;
|
|
|
|
if (!fx.initInstance())
|
|
return false;
|
|
|
|
if (!parentRuntime) {
|
|
sharedImmutableStrings_ = js::SharedImmutableStringsCache::Create();
|
|
if (!sharedImmutableStrings_)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
JSRuntime::destroyRuntime()
|
|
{
|
|
MOZ_ASSERT(!isHeapBusy());
|
|
MOZ_ASSERT(childRuntimeCount == 0);
|
|
|
|
fx.destroyInstance();
|
|
|
|
sharedIntlData.destroyInstance();
|
|
|
|
if (gcInitialized) {
|
|
/*
|
|
* Finish any in-progress GCs first. This ensures the parseWaitingOnGC
|
|
* list is empty in CancelOffThreadParses.
|
|
*/
|
|
JSContext* cx = contextFromMainThread();
|
|
if (JS::IsIncrementalGCInProgress(cx))
|
|
FinishGC(cx);
|
|
|
|
/* Free source hook early, as its destructor may want to delete roots. */
|
|
sourceHook = nullptr;
|
|
|
|
/*
|
|
* Cancel any pending, in progress or completed Ion compilations and
|
|
* parse tasks. Waiting for wasm and compression tasks is done
|
|
* synchronously (on the main thread or during parse tasks), so no
|
|
* explicit canceling is needed for these.
|
|
*/
|
|
CancelOffThreadIonCompile(this);
|
|
CancelOffThreadParses(this);
|
|
|
|
/* Remove persistent GC roots. */
|
|
gc.finishRoots();
|
|
|
|
/*
|
|
* Flag us as being destroyed. This allows the GC to free things like
|
|
* interned atoms and Ion trampolines.
|
|
*/
|
|
beingDestroyed_ = true;
|
|
|
|
/* Allow the GC to release scripts that were being profiled. */
|
|
profilingScripts = false;
|
|
|
|
/* Set the profiler sampler buffer generation to invalid. */
|
|
profilerSampleBufferGen_ = UINT32_MAX;
|
|
|
|
JS::PrepareForFullGC(contextFromMainThread());
|
|
gc.gc(GC_NORMAL, JS::gcreason::DESTROY_RUNTIME);
|
|
}
|
|
|
|
MOZ_ASSERT(ionLazyLinkListSize_ == 0);
|
|
MOZ_ASSERT(ionLazyLinkList_.isEmpty());
|
|
|
|
MOZ_ASSERT(!numExclusiveThreads);
|
|
AutoLockForExclusiveAccess lock(this);
|
|
|
|
/*
|
|
* Even though all objects in the compartment are dead, we may have keep
|
|
* some filenames around because of gcKeepAtoms.
|
|
*/
|
|
FreeScriptData(this, lock);
|
|
|
|
gc.finish();
|
|
atomsCompartment_ = nullptr;
|
|
|
|
js_delete(defaultFreeOp_);
|
|
|
|
js_free(defaultLocale);
|
|
js_delete(jitRuntime_);
|
|
|
|
js_delete(ionPcScriptCache);
|
|
|
|
gc.storeBuffer.disable();
|
|
gc.nursery.disable();
|
|
|
|
#ifdef JS_SIMULATOR
|
|
js::jit::Simulator::Destroy(simulator_);
|
|
#endif
|
|
|
|
DebugOnly<size_t> oldCount = liveRuntimesCount--;
|
|
MOZ_ASSERT(oldCount > 0);
|
|
|
|
#ifdef JS_TRACE_LOGGING
|
|
DestroyTraceLoggerMainThread(this);
|
|
#endif
|
|
|
|
js::TlsPerThreadData.set(nullptr);
|
|
|
|
#ifdef XP_WIN
|
|
if (ownerThreadNative_)
|
|
CloseHandle((HANDLE)ownerThreadNative_);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes* rtSizes)
|
|
{
|
|
// Several tables in the runtime enumerated below can be used off thread.
|
|
AutoLockForExclusiveAccess lock(this);
|
|
|
|
// For now, measure the size of the derived class (JSContext).
|
|
// TODO (bug 1281529): make memory reporting reflect the new
|
|
// JSContext/JSRuntime world better.
|
|
JSContext* cx = unsafeContextFromAnyThread();
|
|
rtSizes->object += mallocSizeOf(cx);
|
|
|
|
rtSizes->atomsTable += atoms(lock).sizeOfIncludingThis(mallocSizeOf);
|
|
|
|
if (!parentRuntime) {
|
|
rtSizes->atomsTable += mallocSizeOf(staticStrings);
|
|
rtSizes->atomsTable += mallocSizeOf(commonNames);
|
|
rtSizes->atomsTable += permanentAtoms->sizeOfIncludingThis(mallocSizeOf);
|
|
}
|
|
|
|
rtSizes->contexts += cx->sizeOfExcludingThis(mallocSizeOf);
|
|
|
|
rtSizes->temporary += tempLifoAlloc.sizeOfExcludingThis(mallocSizeOf);
|
|
|
|
rtSizes->interpreterStack += interpreterStack_.sizeOfExcludingThis(mallocSizeOf);
|
|
|
|
if (MathCache* cache = cx->caches.maybeGetMathCache())
|
|
rtSizes->mathCache += cache->sizeOfIncludingThis(mallocSizeOf);
|
|
|
|
if (sharedImmutableStrings_) {
|
|
rtSizes->sharedImmutableStringsCache +=
|
|
sharedImmutableStrings_->sizeOfExcludingThis(mallocSizeOf);
|
|
}
|
|
|
|
rtSizes->sharedIntlData += sharedIntlData.sizeOfExcludingThis(mallocSizeOf);
|
|
|
|
rtSizes->uncompressedSourceCache +=
|
|
cx->caches.uncompressedSourceCache.sizeOfExcludingThis(mallocSizeOf);
|
|
|
|
|
|
rtSizes->scriptData += scriptDataTable(lock).sizeOfExcludingThis(mallocSizeOf);
|
|
for (ScriptDataTable::Range r = scriptDataTable(lock).all(); !r.empty(); r.popFront())
|
|
rtSizes->scriptData += mallocSizeOf(r.front());
|
|
|
|
if (jitRuntime_) {
|
|
jitRuntime_->execAlloc().addSizeOfCode(&rtSizes->code);
|
|
jitRuntime_->backedgeExecAlloc().addSizeOfCode(&rtSizes->code);
|
|
}
|
|
|
|
rtSizes->gc.marker += gc.marker.sizeOfExcludingThis(mallocSizeOf);
|
|
rtSizes->gc.nurseryCommitted += gc.nursery.sizeOfHeapCommitted();
|
|
rtSizes->gc.nurseryMallocedBuffers += gc.nursery.sizeOfMallocedBuffers(mallocSizeOf);
|
|
gc.storeBuffer.addSizeOfExcludingThis(mallocSizeOf, &rtSizes->gc);
|
|
}
|
|
|
|
static bool
|
|
InvokeInterruptCallback(JSContext* cx)
|
|
{
|
|
MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
|
|
|
|
cx->runtime()->gc.gcIfRequested();
|
|
|
|
// A worker thread may have requested an interrupt after finishing an Ion
|
|
// compilation.
|
|
jit::AttachFinishedCompilations(cx);
|
|
|
|
// Important: Additional callbacks can occur inside the callback handler
|
|
// if it re-enters the JS engine. The embedding must ensure that the
|
|
// callback is disconnected before attempting such re-entry.
|
|
if (cx->runtime()->interruptCallbackDisabled)
|
|
return true;
|
|
|
|
bool stop = false;
|
|
for (JSInterruptCallback cb : cx->runtime()->interruptCallbacks) {
|
|
if (!cb(cx))
|
|
stop = true;
|
|
}
|
|
|
|
if (!stop) {
|
|
// Debugger treats invoking the interrupt callback as a "step", so
|
|
// invoke the onStep handler.
|
|
if (cx->compartment()->isDebuggee()) {
|
|
ScriptFrameIter iter(cx);
|
|
if (!iter.done() &&
|
|
cx->compartment() == iter.compartment() &&
|
|
iter.script()->stepModeEnabled())
|
|
{
|
|
RootedValue rval(cx);
|
|
switch (Debugger::onSingleStep(cx, &rval)) {
|
|
case JSTRAP_ERROR:
|
|
return false;
|
|
case JSTRAP_CONTINUE:
|
|
return true;
|
|
case JSTRAP_RETURN:
|
|
// See note in Debugger::propagateForcedReturn.
|
|
Debugger::propagateForcedReturn(cx, iter.abstractFramePtr(), rval);
|
|
return false;
|
|
case JSTRAP_THROW:
|
|
cx->setPendingException(rval);
|
|
return false;
|
|
default:;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// No need to set aside any pending exception here: ComputeStackString
|
|
// already does that.
|
|
JSString* stack = ComputeStackString(cx);
|
|
JSFlatString* flat = stack ? stack->ensureFlat(cx) : nullptr;
|
|
|
|
const char16_t* chars;
|
|
AutoStableStringChars stableChars(cx);
|
|
if (flat && stableChars.initTwoByte(cx, flat))
|
|
chars = stableChars.twoByteRange().begin().get();
|
|
else
|
|
chars = u"(stack not available)";
|
|
JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
|
|
JSMSG_TERMINATED, chars);
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
JSRuntime::requestInterrupt(InterruptMode mode)
|
|
{
|
|
interrupt_ = true;
|
|
jitStackLimit_ = UINTPTR_MAX;
|
|
|
|
if (mode == JSRuntime::RequestInterruptUrgent) {
|
|
// If this interrupt is urgent (slow script dialog and garbage
|
|
// collection among others), take additional steps to
|
|
// interrupt corner cases where the above fields are not
|
|
// regularly polled. Wake both ilooping JIT code and
|
|
// Atomics.wait().
|
|
fx.lock();
|
|
if (fx.isWaiting())
|
|
fx.notify(FutexRuntime::NotifyForJSInterrupt);
|
|
fx.unlock();
|
|
InterruptRunningJitCode(this);
|
|
}
|
|
}
|
|
|
|
bool
|
|
JSRuntime::handleInterrupt(JSContext* cx)
|
|
{
|
|
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
|
|
if (interrupt_ || jitStackLimit_ == UINTPTR_MAX) {
|
|
interrupt_ = false;
|
|
cx->resetJitStackLimit();
|
|
return InvokeInterruptCallback(cx);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JSRuntime::setDefaultLocale(const char* locale)
|
|
{
|
|
if (!locale)
|
|
return false;
|
|
resetDefaultLocale();
|
|
defaultLocale = JS_strdup(contextFromMainThread(), locale);
|
|
return defaultLocale != nullptr;
|
|
}
|
|
|
|
void
|
|
JSRuntime::resetDefaultLocale()
|
|
{
|
|
js_free(defaultLocale);
|
|
defaultLocale = nullptr;
|
|
}
|
|
|
|
const char*
|
|
JSRuntime::getDefaultLocale()
|
|
{
|
|
if (defaultLocale)
|
|
return defaultLocale;
|
|
|
|
const char* locale;
|
|
#ifdef HAVE_SETLOCALE
|
|
locale = setlocale(LC_ALL, nullptr);
|
|
#else
|
|
locale = getenv("LANG");
|
|
#endif
|
|
// convert to a well-formed BCP 47 language tag
|
|
if (!locale || !strcmp(locale, "C"))
|
|
locale = "und";
|
|
|
|
char* lang = JS_strdup(contextFromMainThread(), locale);
|
|
if (!lang)
|
|
return nullptr;
|
|
|
|
char* p;
|
|
if ((p = strchr(lang, '.')))
|
|
*p = '\0';
|
|
while ((p = strchr(lang, '_')))
|
|
*p = '-';
|
|
|
|
defaultLocale = lang;
|
|
return defaultLocale;
|
|
}
|
|
|
|
void
|
|
JSRuntime::traceSharedIntlData(JSTracer* trc)
|
|
{
|
|
sharedIntlData.trace(trc);
|
|
}
|
|
|
|
void
|
|
JSRuntime::triggerActivityCallback(bool active)
|
|
{
|
|
if (!activityCallback)
|
|
return;
|
|
|
|
/*
|
|
* The activity callback must not trigger a GC: it would create a cirular
|
|
* dependency between entering a request and Rooted's requirement of being
|
|
* in a request. In practice this callback already cannot trigger GC. The
|
|
* suppression serves to inform the exact rooting hazard analysis of this
|
|
* property and ensures that it remains true in the future.
|
|
*/
|
|
AutoSuppressGC suppress(contextFromMainThread());
|
|
|
|
activityCallback(activityCallbackArg, active);
|
|
}
|
|
|
|
FreeOp::FreeOp(JSRuntime* maybeRuntime)
|
|
: JSFreeOp(maybeRuntime)
|
|
{
|
|
MOZ_ASSERT_IF(maybeRuntime, CurrentThreadCanAccessRuntime(maybeRuntime));
|
|
}
|
|
|
|
FreeOp::~FreeOp()
|
|
{
|
|
for (size_t i = 0; i < freeLaterList.length(); i++)
|
|
free_(freeLaterList[i]);
|
|
|
|
if (!jitPoisonRanges.empty())
|
|
jit::ExecutableAllocator::poisonCode(runtime(), jitPoisonRanges);
|
|
}
|
|
|
|
bool
|
|
FreeOp::isDefaultFreeOp() const
|
|
{
|
|
return runtime_ && runtime_->defaultFreeOp() == this;
|
|
}
|
|
|
|
JSObject*
|
|
JSRuntime::getIncumbentGlobal(JSContext* cx)
|
|
{
|
|
MOZ_ASSERT(cx->runtime()->getIncumbentGlobalCallback,
|
|
"Must set a callback using JS_SetGetIncumbentGlobalCallback before using Promises");
|
|
|
|
return cx->runtime()->getIncumbentGlobalCallback(cx);
|
|
}
|
|
|
|
bool
|
|
JSRuntime::enqueuePromiseJob(JSContext* cx, HandleFunction job, HandleObject promise,
|
|
HandleObject incumbentGlobal)
|
|
{
|
|
MOZ_ASSERT(cx->runtime()->enqueuePromiseJobCallback,
|
|
"Must set a callback using JS_SetEnqeueuPromiseJobCallback before using Promises");
|
|
MOZ_ASSERT_IF(incumbentGlobal, !IsWrapper(incumbentGlobal) && !IsWindowProxy(incumbentGlobal));
|
|
|
|
void* data = cx->runtime()->enqueuePromiseJobCallbackData;
|
|
RootedObject allocationSite(cx);
|
|
if (promise) {
|
|
RootedObject unwrappedPromise(cx, promise);
|
|
// While the job object is guaranteed to be unwrapped, the promise
|
|
// might be wrapped. See the comments in
|
|
// intrinsic_EnqueuePromiseReactionJob for details.
|
|
if (IsWrapper(promise))
|
|
unwrappedPromise = UncheckedUnwrap(promise);
|
|
if (unwrappedPromise->is<PromiseObject>())
|
|
allocationSite = JS::GetPromiseAllocationSite(unwrappedPromise);
|
|
}
|
|
return cx->runtime()->enqueuePromiseJobCallback(cx, job, allocationSite, incumbentGlobal, data);
|
|
}
|
|
|
|
void
|
|
JSRuntime::addUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise)
|
|
{
|
|
MOZ_ASSERT(promise->is<PromiseObject>());
|
|
if (!cx->runtime()->promiseRejectionTrackerCallback)
|
|
return;
|
|
|
|
void* data = cx->runtime()->promiseRejectionTrackerCallbackData;
|
|
cx->runtime()->promiseRejectionTrackerCallback(cx, promise,
|
|
PromiseRejectionHandlingState::Unhandled, data);
|
|
}
|
|
|
|
void
|
|
JSRuntime::removeUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise)
|
|
{
|
|
MOZ_ASSERT(promise->is<PromiseObject>());
|
|
if (!cx->runtime()->promiseRejectionTrackerCallback)
|
|
return;
|
|
|
|
void* data = cx->runtime()->promiseRejectionTrackerCallbackData;
|
|
cx->runtime()->promiseRejectionTrackerCallback(cx, promise,
|
|
PromiseRejectionHandlingState::Handled, data);
|
|
}
|
|
|
|
mozilla::non_crypto::XorShift128PlusRNG&
|
|
JSRuntime::randomKeyGenerator()
|
|
{
|
|
MOZ_ASSERT(CurrentThreadCanAccessRuntime(this));
|
|
if (randomKeyGenerator_.isNothing()) {
|
|
mozilla::Array<uint64_t, 2> seed;
|
|
GenerateXorShift128PlusSeed(seed);
|
|
randomKeyGenerator_.emplace(seed[0], seed[1]);
|
|
}
|
|
return randomKeyGenerator_.ref();
|
|
}
|
|
|
|
mozilla::HashCodeScrambler
|
|
JSRuntime::randomHashCodeScrambler()
|
|
{
|
|
auto& rng = randomKeyGenerator();
|
|
return mozilla::HashCodeScrambler(rng.next(), rng.next());
|
|
}
|
|
|
|
mozilla::non_crypto::XorShift128PlusRNG
|
|
JSRuntime::forkRandomKeyGenerator()
|
|
{
|
|
auto& rng = randomKeyGenerator();
|
|
return mozilla::non_crypto::XorShift128PlusRNG(rng.next(), rng.next());
|
|
}
|
|
|
|
void
|
|
JSRuntime::updateMallocCounter(size_t nbytes)
|
|
{
|
|
updateMallocCounter(nullptr, nbytes);
|
|
}
|
|
|
|
void
|
|
JSRuntime::updateMallocCounter(JS::Zone* zone, size_t nbytes)
|
|
{
|
|
gc.updateMallocCounter(zone, nbytes);
|
|
}
|
|
|
|
JS_FRIEND_API(void*)
|
|
JSRuntime::onOutOfMemory(AllocFunction allocFunc, size_t nbytes, void* reallocPtr,
|
|
JSContext* maybecx)
|
|
{
|
|
MOZ_ASSERT_IF(allocFunc != AllocFunction::Realloc, !reallocPtr);
|
|
MOZ_ASSERT(CurrentThreadCanAccessRuntime(this));
|
|
|
|
if (isHeapBusy())
|
|
return nullptr;
|
|
|
|
if (!oom::IsSimulatedOOMAllocation()) {
|
|
/*
|
|
* Retry when we are done with the background sweeping and have stopped
|
|
* all the allocations and released the empty GC chunks.
|
|
*/
|
|
gc.onOutOfMallocMemory();
|
|
void* p;
|
|
switch (allocFunc) {
|
|
case AllocFunction::Malloc:
|
|
p = js_malloc(nbytes);
|
|
break;
|
|
case AllocFunction::Calloc:
|
|
p = js_calloc(nbytes);
|
|
break;
|
|
case AllocFunction::Realloc:
|
|
p = js_realloc(reallocPtr, nbytes);
|
|
break;
|
|
default:
|
|
MOZ_CRASH();
|
|
}
|
|
if (p)
|
|
return p;
|
|
}
|
|
|
|
if (maybecx)
|
|
ReportOutOfMemory(maybecx);
|
|
return nullptr;
|
|
}
|
|
|
|
void*
|
|
JSRuntime::onOutOfMemoryCanGC(AllocFunction allocFunc, size_t bytes, void* reallocPtr)
|
|
{
|
|
if (largeAllocationFailureCallback && bytes >= LARGE_ALLOCATION)
|
|
largeAllocationFailureCallback(largeAllocationFailureCallbackData);
|
|
return onOutOfMemory(allocFunc, bytes, reallocPtr);
|
|
}
|
|
|
|
bool
|
|
JSRuntime::activeGCInAtomsZone()
|
|
{
|
|
Zone* zone = atomsCompartment_->zone();
|
|
return zone->needsIncrementalBarrier() || zone->wasGCStarted();
|
|
}
|
|
|
|
void
|
|
JSRuntime::setUsedByExclusiveThread(Zone* zone)
|
|
{
|
|
MOZ_ASSERT(!zone->usedByExclusiveThread);
|
|
zone->usedByExclusiveThread = true;
|
|
numExclusiveThreads++;
|
|
}
|
|
|
|
void
|
|
JSRuntime::clearUsedByExclusiveThread(Zone* zone)
|
|
{
|
|
MOZ_ASSERT(zone->usedByExclusiveThread);
|
|
zone->usedByExclusiveThread = false;
|
|
numExclusiveThreads--;
|
|
if (gc.fullGCForAtomsRequested() && !keepAtoms())
|
|
gc.triggerFullGCForAtoms();
|
|
}
|
|
|
|
bool
|
|
js::CurrentThreadCanAccessRuntime(const JSRuntime* rt)
|
|
{
|
|
return rt->ownerThread_ == js::ThisThread::GetId();
|
|
}
|
|
|
|
bool
|
|
js::CurrentThreadCanAccessZone(Zone* zone)
|
|
{
|
|
if (CurrentThreadCanAccessRuntime(zone->runtime_))
|
|
return true;
|
|
|
|
// Only zones in use by an exclusive thread can be used off the main thread.
|
|
// We don't keep track of which thread owns such zones though, so this check
|
|
// is imperfect.
|
|
return zone->usedByExclusiveThread;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
bool
|
|
js::CurrentThreadIsPerformingGC()
|
|
{
|
|
return TlsPerThreadData.get()->performingGC;
|
|
}
|
|
#endif
|
|
|
|
JS_FRIEND_API(void)
|
|
JS::UpdateJSContextProfilerSampleBufferGen(JSContext* cx, uint32_t generation,
|
|
uint32_t lapCount)
|
|
{
|
|
cx->setProfilerSampleBufferGen(generation);
|
|
cx->updateProfilerSampleBufferLapCount(lapCount);
|
|
}
|
|
|
|
JS_FRIEND_API(bool)
|
|
JS::IsProfilingEnabledForContext(JSContext* cx)
|
|
{
|
|
MOZ_ASSERT(cx);
|
|
return cx->spsProfiler.enabled();
|
|
}
|
|
|
|
JSRuntime::IonBuilderList&
|
|
JSRuntime::ionLazyLinkList()
|
|
{
|
|
MOZ_ASSERT(TlsPerThreadData.get()->runtimeFromMainThread(),
|
|
"Should only be mutated by the main thread.");
|
|
return ionLazyLinkList_;
|
|
}
|
|
|
|
void
|
|
JSRuntime::ionLazyLinkListRemove(jit::IonBuilder* builder)
|
|
{
|
|
MOZ_ASSERT(TlsPerThreadData.get()->runtimeFromMainThread(),
|
|
"Should only be mutated by the main thread.");
|
|
MOZ_ASSERT(ionLazyLinkListSize_ > 0);
|
|
|
|
builder->removeFrom(ionLazyLinkList());
|
|
ionLazyLinkListSize_--;
|
|
|
|
MOZ_ASSERT(ionLazyLinkList().isEmpty() == (ionLazyLinkListSize_ == 0));
|
|
}
|
|
|
|
void
|
|
JSRuntime::ionLazyLinkListAdd(jit::IonBuilder* builder)
|
|
{
|
|
MOZ_ASSERT(TlsPerThreadData.get()->runtimeFromMainThread(),
|
|
"Should only be mutated by the main thread.");
|
|
ionLazyLinkList().insertFront(builder);
|
|
ionLazyLinkListSize_++;
|
|
}
|
|
|
|
JSContext*
|
|
PerThreadData::contextFromMainThread()
|
|
{
|
|
return runtime_->contextFromMainThread();
|
|
}
|