Bug 1091916 - simplify the interrupt/jitStackLimit situation (r=bhackett)

--HG--
extra : rebase_source : 44ac5dbc77174414f4596aa7c318fdaa23b8e292
This commit is contained in:
Luke Wagner 2014-10-30 17:35:35 -05:00
parent 50868f204d
commit 0bbf234774
22 changed files with 245 additions and 301 deletions

View File

@ -500,7 +500,7 @@ AsmJSHandleExecutionInterrupt()
{
AsmJSActivation *act = PerThreadData::innermostAsmJSActivation();
act->module().setInterrupted(true);
bool ret = HandleExecutionInterrupt(act->cx());
bool ret = CheckForInterrupt(act->cx());
act->module().setInterrupted(false);
return ret;
}
@ -673,8 +673,8 @@ AddressOf(AsmJSImmKind kind, ExclusiveContext *cx)
switch (kind) {
case AsmJSImm_Runtime:
return cx->runtimeAddressForJit();
case AsmJSImm_RuntimeInterrupt:
return cx->runtimeAddressOfInterrupt();
case AsmJSImm_RuntimeInterruptUint32:
return cx->runtimeAddressOfInterruptUint32();
case AsmJSImm_StackLimit:
return cx->stackLimitAddressForJitCode(StackForUntrustedScript);
case AsmJSImm_ReportOverRecursed:

View File

@ -152,7 +152,7 @@ NativeRegExpMacroAssembler::GenerateCode(JSContext *cx, bool match_only)
// Check if we have space on the stack.
Label stack_ok;
void *stack_limit = &runtime->mainThread.jitStackLimit;
void *stack_limit = runtime->mainThread.addressOfJitStackLimit();
masm.branchPtr(Assembler::Below, AbsoluteAddress(stack_limit), StackPointer, &stack_ok);
// Exit with an exception. There is not enough space on the stack
@ -502,7 +502,7 @@ NativeRegExpMacroAssembler::Backtrack()
// Check for an interrupt.
Label noInterrupt;
masm.branch32(Assembler::Equal,
AbsoluteAddress(&runtime->interrupt), Imm32(0),
AbsoluteAddress(runtime->addressOfInterruptUint32()), Imm32(0),
&noInterrupt);
masm.movePtr(ImmWord(RegExpRunStatus_Error), temp0);
masm.jump(&exit_label_);

View File

@ -500,7 +500,7 @@ bool
BaselineCompiler::emitStackCheck(bool earlyCheck)
{
Label skipCall;
uintptr_t *limitAddr = &cx->runtime()->mainThread.jitStackLimit;
void *limitAddr = cx->runtime()->mainThread.addressOfJitStackLimit();
uint32_t slotsSize = script->nslots() * sizeof(Value);
uint32_t tolerance = earlyCheck ? slotsSize : 0;
@ -650,7 +650,7 @@ BaselineCompiler::emitInterruptCheck()
frame.syncStack(0);
Label done;
void *interrupt = (void *)&cx->runtime()->interrupt;
void *interrupt = cx->runtimeAddressOfInterruptUint32();
masm.branch32(Assembler::Equal, AbsoluteAddress(interrupt), Imm32(0), &done);
prepareVMCall();

View File

@ -3772,7 +3772,7 @@ CodeGenerator::visitCheckOverRecursedPar(LCheckOverRecursedPar *lir)
Register tempReg = ToRegister(lir->getTempReg());
masm.loadPtr(Address(cxReg, offsetof(ForkJoinContext, perThreadData)), tempReg);
masm.loadPtr(Address(tempReg, offsetof(PerThreadData, jitStackLimit)), tempReg);
masm.loadPtr(Address(tempReg, PerThreadData::offsetOfJitStackLimit()), tempReg);
// Conditional forward (unlikely) branch to failure.
CheckOverRecursedFailure *ool = new(alloc()) CheckOverRecursedFailure(lir);
@ -9950,7 +9950,7 @@ CodeGenerator::visitInterruptCheck(LInterruptCheck *lir)
if (!ool)
return false;
AbsoluteAddress interruptAddr(GetIonContext()->runtime->addressOfInterrupt());
AbsoluteAddress interruptAddr(GetIonContext()->runtime->addressOfInterruptUint32());
masm.branch32(Assembler::NotEqual, interruptAddr, Imm32(0), ool->entry());
masm.bind(ool->rejoin());
return true;
@ -9960,8 +9960,8 @@ bool
CodeGenerator::visitAsmJSInterruptCheck(LAsmJSInterruptCheck *lir)
{
Register scratch = ToRegister(lir->scratch());
masm.movePtr(AsmJSImmPtr(AsmJSImm_RuntimeInterrupt), scratch);
masm.load8ZeroExtend(Address(scratch, 0), scratch);
masm.movePtr(AsmJSImmPtr(AsmJSImm_RuntimeInterruptUint32), scratch);
masm.load32(Address(scratch, 0), scratch);
Label rejoin;
masm.branch32(Assembler::Equal, scratch, Imm32(0), &rejoin);
{

View File

@ -43,7 +43,7 @@ CompileRuntime::addressOfJitTop()
const void *
CompileRuntime::addressOfJitStackLimit()
{
return &runtime()->mainThread.jitStackLimit;
return runtime()->mainThread.addressOfJitStackLimit();
}
const void *
@ -73,15 +73,15 @@ CompileRuntime::addressOfGCZeal()
#endif
const void *
CompileRuntime::addressOfInterrupt()
CompileRuntime::addressOfInterruptUint32()
{
return &runtime()->interrupt;
return runtime()->addressOfInterruptUint32();
}
const void *
CompileRuntime::addressOfInterruptPar()
CompileRuntime::addressOfInterruptParUint32()
{
return &runtime()->interruptPar;
return runtime()->addressOfInterruptParUint32();
}
const void *

View File

@ -50,8 +50,8 @@ class CompileRuntime
const void *addressOfGCZeal();
#endif
const void *addressOfInterrupt();
const void *addressOfInterruptPar();
const void *addressOfInterruptUint32();
const void *addressOfInterruptParUint32();
const void *addressOfThreadPool();

View File

@ -428,7 +428,7 @@ JitRuntime::ensureIonCodeAccessible(JSRuntime *rt)
ionCodeProtected_ = false;
}
if (rt->interrupt) {
if (rt->hasPendingInterrupt()) {
// The interrupt handler needs to be invoked by this thread, but we may
// be inside a signal handler and have no idea what is above us on the
// stack (probably we are executing Ion code at an arbitrary point, but
@ -1162,7 +1162,7 @@ IonScript::copyPatchableBackedges(JSContext *cx, JitCode *code,
// whether an interrupt is currently desired, matching the targets
// established by ensureIonCodeAccessible() above. We don't handle the
// interrupt immediately as the interrupt lock is held here.
if (cx->runtime()->interrupt)
if (cx->runtime()->hasPendingInterrupt())
PatchBackedge(backedge, interruptCheck, JitRuntime::BackedgeInterruptCheck);
else
PatchBackedge(backedge, loopHeader, JitRuntime::BackedgeLoopHeader);

View File

@ -1238,7 +1238,7 @@ MacroAssembler::loadStringChar(Register str, Register index, Register output)
void
MacroAssembler::checkInterruptFlagPar(Register tempReg, Label *fail)
{
movePtr(ImmPtr(GetIonContext()->runtime->addressOfInterruptPar()), tempReg);
movePtr(ImmPtr(GetIonContext()->runtime->addressOfInterruptParUint32()), tempReg);
branch32(Assembler::NonZero, Address(tempReg, 0), Imm32(0), fail);
}

View File

@ -147,7 +147,7 @@ jit::CheckOverRecursedPar(ForkJoinContext *cx)
}
#endif
if (!JS_CHECK_STACK_SIZE(cx->perThreadData->jitStackLimit, &stackDummy_)) {
if (!JS_CHECK_STACK_SIZE(cx->perThreadData->jitStackLimit(), &stackDummy_)) {
cx->bailoutRecord->joinCause(ParallelBailoutOverRecursed);
return false;
}

View File

@ -113,28 +113,17 @@ NewGCObject(JSContext *cx, gc::AllocKind allocKind, gc::InitialHeap initialHeap)
bool
CheckOverRecursed(JSContext *cx)
{
// IonMonkey's stackLimit is equal to nativeStackLimit by default. When we
// request an interrupt, we set the jitStackLimit to nullptr, which causes
// the stack limit check to fail.
//
// There are two states we're concerned about here:
// (1) The interrupt bit is set, and we need to fire the interrupt callback.
// (2) The stack limit has been exceeded, and we need to throw an error.
//
// Note that we can reach here if jitStackLimit is MAXADDR, but interrupt
// has not yet been set to 1. That's okay; it will be set to 1 very shortly,
// and in the interim we might just fire a few useless calls to
// CheckOverRecursed.
// We just failed the jitStackLimit check. There are two possible reasons:
// - jitStackLimit was the real stack limit and we're over-recursed
// - jitStackLimit was set to UINTPTR_MAX by JSRuntime::requestInterrupt
// and we need to call JSRuntime::handleInterrupt.
#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, 0, return false);
#else
JS_CHECK_RECURSION(cx, return false);
#endif
if (cx->runtime()->interrupt)
return InterruptCheck(cx);
return true;
gc::MaybeVerifyBarriers(cx);
return cx->runtime()->handleInterrupt(cx);
}
// This function can get called in two contexts. In the usual context, it's
@ -178,10 +167,8 @@ CheckOverRecursedWithExtra(JSContext *cx, BaselineFrame *frame,
JS_CHECK_RECURSION_WITH_SP(cx, checkSp, return false);
#endif
if (cx->runtime()->interrupt)
return InterruptCheck(cx);
return true;
gc::MaybeVerifyBarriers(cx);
return cx->runtime()->handleInterrupt(cx);
}
bool

View File

@ -783,7 +783,7 @@ enum AsmJSImmKind
AsmJSImm_PowD = AsmJSExit::Builtin_PowD,
AsmJSImm_ATan2D = AsmJSExit::Builtin_ATan2D,
AsmJSImm_Runtime,
AsmJSImm_RuntimeInterrupt,
AsmJSImm_RuntimeInterruptUint32,
AsmJSImm_StackLimit,
AsmJSImm_ReportOverRecursed,
AsmJSImm_OnDetached,

View File

@ -2031,68 +2031,48 @@ JS_GetExternalStringFinalizer(JSString *str)
}
static void
SetNativeStackQuota(JSRuntime *rt, StackKind kind, size_t stackSize)
SetNativeStackQuotaAndLimit(JSRuntime *rt, StackKind kind, size_t stackSize)
{
rt->nativeStackQuota[kind] = stackSize;
if (rt->nativeStackBase)
RecomputeStackLimit(rt, kind);
}
void
js::RecomputeStackLimit(JSRuntime *rt, StackKind kind)
{
size_t stackSize = rt->nativeStackQuota[kind];
#if JS_STACK_GROWTH_DIRECTION > 0
if (stackSize == 0) {
rt->mainThread.nativeStackLimit[kind] = UINTPTR_MAX;
} else {
MOZ_ASSERT(rt->nativeStackBase <= size_t(-1) - stackSize);
rt->mainThread.nativeStackLimit[kind] =
rt->nativeStackBase + stackSize - 1;
rt->mainThread.nativeStackLimit[kind] = rt->nativeStackBase + stackSize - 1;
}
#else
if (stackSize == 0) {
rt->mainThread.nativeStackLimit[kind] = 0;
} else {
MOZ_ASSERT(rt->nativeStackBase >= stackSize);
rt->mainThread.nativeStackLimit[kind] =
rt->nativeStackBase - (stackSize - 1);
rt->mainThread.nativeStackLimit[kind] = rt->nativeStackBase - (stackSize - 1);
}
#endif
// If there's no pending interrupt request set on the runtime's main thread's
// jitStackLimit, then update it so that it reflects the new nativeStacklimit.
//
// Note that, for now, we use the untrusted limit for ion. This is fine,
// because it's the most conservative limit, and if we hit it, we'll bail
// out of ion into the interpeter, which will do a proper recursion check.
if (kind == StackForUntrustedScript) {
JSRuntime::AutoLockForInterrupt lock(rt);
if (rt->mainThread.jitStackLimit != uintptr_t(-1)) {
rt->mainThread.jitStackLimit = rt->mainThread.nativeStackLimit[kind];
#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
rt->mainThread.jitStackLimit = jit::Simulator::StackLimit();
#endif
}
}
}
JS_PUBLIC_API(void)
JS_SetNativeStackQuota(JSRuntime *rt, size_t systemCodeStackSize,
size_t trustedScriptStackSize,
JS_SetNativeStackQuota(JSRuntime *rt, size_t systemCodeStackSize, size_t trustedScriptStackSize,
size_t untrustedScriptStackSize)
{
MOZ_ASSERT_IF(trustedScriptStackSize,
trustedScriptStackSize < systemCodeStackSize);
MOZ_ASSERT(rt->requestDepth == 0);
if (!trustedScriptStackSize)
trustedScriptStackSize = systemCodeStackSize;
MOZ_ASSERT_IF(untrustedScriptStackSize,
untrustedScriptStackSize < trustedScriptStackSize);
else
MOZ_ASSERT(trustedScriptStackSize < systemCodeStackSize);
if (!untrustedScriptStackSize)
untrustedScriptStackSize = trustedScriptStackSize;
SetNativeStackQuota(rt, StackForSystemCode, systemCodeStackSize);
SetNativeStackQuota(rt, StackForTrustedScript, trustedScriptStackSize);
SetNativeStackQuota(rt, StackForUntrustedScript, untrustedScriptStackSize);
else
MOZ_ASSERT(untrustedScriptStackSize < trustedScriptStackSize);
SetNativeStackQuotaAndLimit(rt, StackForSystemCode, systemCodeStackSize);
SetNativeStackQuotaAndLimit(rt, StackForTrustedScript, trustedScriptStackSize);
SetNativeStackQuotaAndLimit(rt, StackForUntrustedScript, untrustedScriptStackSize);
rt->mainThread.initJitStackLimit();
}
/************************************************************************/

View File

@ -2272,6 +2272,9 @@ JS_GetExternalStringFinalizer(JSString *str);
* The stack quotas for each kind of code should be monotonically descending,
* and may be specified with this function. If 0 is passed for a given kind
* of code, it defaults to the value of the next-highest-priority kind.
*
* This function may only be called immediately after the runtime is initialized
* and before any code is executed and/or interrupts requested.
*/
extern JS_PUBLIC_API(void)
JS_SetNativeStackQuota(JSRuntime *cx, size_t systemCodeStackSize,

View File

@ -41,7 +41,6 @@
#include "gc/Marking.h"
#include "jit/Ion.h"
#include "js/CharacterEncoding.h"
#include "vm/Debugger.h"
#include "vm/HelperThreads.h"
#include "vm/Shape.h"
@ -971,90 +970,6 @@ js_GetErrorMessage(void *userRef, const unsigned errorNumber)
return nullptr;
}
bool
js::InvokeInterruptCallback(JSContext *cx)
{
MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
JSRuntime *rt = cx->runtime();
MOZ_ASSERT(rt->interrupt);
// Reset the callback counter first, then run GC and yield. If another
// thread is racing us here we will accumulate another callback request
// which will be serviced at the next opportunity.
rt->interrupt = false;
// IonMonkey sets its stack limit to UINTPTR_MAX to trigger interrupt
// callbacks.
rt->resetJitStackLimit();
cx->gcIfNeeded();
rt->interruptPar = false;
// 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.
JSInterruptCallback cb = cx->runtime()->interruptCallback;
if (!cb)
return true;
if (cb(cx)) {
// Debugger treats invoking the interrupt callback as a "step", so
// invoke the onStep handler.
if (cx->compartment()->debugMode()) {
ScriptFrameIter iter(cx);
if (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().start().get();
else
chars = MOZ_UTF16("(stack not available)");
JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_WARNING, js_GetErrorMessage, nullptr,
JSMSG_TERMINATED, chars);
return false;
}
bool
js::HandleExecutionInterrupt(JSContext *cx)
{
if (cx->runtime()->interrupt)
return InvokeInterruptCallback(cx);
return true;
}
ThreadSafeContext::ThreadSafeContext(JSRuntime *rt, PerThreadData *pt, ContextKind kind)
: ContextFriendFields(rt),
contextKind_(kind),

View File

@ -289,7 +289,7 @@ struct ThreadSafeContext : ContextFriendFields,
PropertyName *emptyString() { return runtime_->emptyString; }
FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); }
void *runtimeAddressForJit() { return runtime_; }
void *runtimeAddressOfInterrupt() { return &runtime_->interrupt; }
void *runtimeAddressOfInterruptUint32() { return runtime_->addressOfInterruptUint32(); }
void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; }
void *stackLimitAddressForJitCode(StackKind kind);
size_t gcSystemPageSize() { return gc::SystemPageSize(); }
@ -782,33 +782,15 @@ extern const JSErrorFormatString js_ErrorFormatString[JSErr_Limit];
namespace js {
/*
* Invoke the interrupt callback and return false if the current execution
* is to be terminated.
*/
bool
InvokeInterruptCallback(JSContext *cx);
bool
HandleExecutionInterrupt(JSContext *cx);
/*
* Process any pending interrupt requests. Long-running inner loops in C++ must
* call this periodically to make sure they are interruptible --- that is, to
* make sure they do not prevent the slow script dialog from appearing.
*
* This can run a full GC or call the interrupt callback, which could do
* anything. In the browser, it displays the slow script dialog.
*
* If this returns true, the caller can continue; if false, the caller must
* break out of its loop. This happens if, for example, the user clicks "Stop
* script" on the slow script dialog; treat it as an uncatchable error.
*/
MOZ_ALWAYS_INLINE bool
CheckForInterrupt(JSContext *cx)
{
MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
return !cx->runtime()->interrupt || InvokeInterruptCallback(cx);
// Add an inline fast-path since we have to check for interrupts in some hot
// C++ loops of library builtins.
JSRuntime *rt = cx->runtime();
if (rt->hasPendingInterrupt())
return rt->handleInterrupt(cx);
return true;
}
/************************************************************************/

View File

@ -526,7 +526,7 @@ CheckAllocatorState(ThreadSafeContext *cx, AllocKind kind)
rt->gc.runDebugGC();
#endif
if (rt->interrupt) {
if (rt->hasPendingInterrupt()) {
// Invoking the interrupt callback can fail and we can't usefully
// handle that here. Just check in case we need to collect instead.
ncx->gcIfNeeded();

View File

@ -18,6 +18,7 @@ inline uintptr_t
GetNativeStackBase()
{
uintptr_t stackBase = reinterpret_cast<uintptr_t>(GetNativeStackBaseImpl());
MOZ_ASSERT(stackBase != 0);
MOZ_ASSERT(stackBase % sizeof(void *) == 0);
return stackBase;
}

View File

@ -1440,7 +1440,7 @@ ForkJoinShared::execute()
// Sometimes a GC request occurs *just before* we enter into the
// parallel section. Rather than enter into the parallel section
// and then abort, we just check here and abort early.
if (cx_->runtime()->interruptPar)
if (cx_->runtime()->hasPendingInterruptPar())
return TP_RETRY_SEQUENTIALLY;
AutoLockMonitor lock(*this);
@ -1518,7 +1518,7 @@ ForkJoinShared::executeFromWorker(ThreadPoolWorker *worker, uintptr_t stackLimit
// Don't use setIonStackLimit() because that acquires the ionStackLimitLock, and the
// lock has not been initialized in these cases.
thisThread.jitStackLimit = stackLimit;
thisThread.initJitStackLimitPar(stackLimit);
executePortion(&thisThread, worker);
TlsPerThreadData.set(nullptr);
@ -1551,7 +1551,7 @@ ForkJoinShared::executeFromMainThread(ThreadPoolWorker *worker)
//
// Thus, use GetNativeStackLimit instead of just propagating the
// main thread's.
thisThread.jitStackLimit = GetNativeStackLimit(cx_);
thisThread.initJitStackLimitPar(GetNativeStackLimit(cx_));
executePortion(&thisThread, worker);
TlsPerThreadData.set(oldData);
@ -1647,7 +1647,7 @@ ForkJoinShared::executePortion(PerThreadData *perThread, ThreadPoolWorker *worke
void
ForkJoinShared::setAbortFlagDueToInterrupt(ForkJoinContext &cx)
{
MOZ_ASSERT(cx_->runtime()->interruptPar);
MOZ_ASSERT(cx_->runtime()->hasPendingInterruptPar());
// The GC Needed flag should not be set during parallel
// execution. Instead, one of the requestGC() or
// requestZoneGC() methods should be invoked.
@ -1826,7 +1826,7 @@ ForkJoinContext::hasAcquiredJSContext() const
bool
ForkJoinContext::check()
{
if (runtime()->interruptPar) {
if (runtime()->hasPendingInterruptPar()) {
shared_->setAbortFlagDueToInterrupt(*this);
return false;
}
@ -2273,13 +2273,6 @@ js::ParallelTestsShouldPass(JSContext *cx)
cx->runtime()->gcZeal() == 0;
}
void
js::RequestInterruptForForkJoin(JSRuntime *rt, JSRuntime::InterruptMode mode)
{
if (mode != JSRuntime::RequestInterruptAnyThreadDontStopIon)
rt->interruptPar = true;
}
bool
js::intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp)
{

View File

@ -546,8 +546,6 @@ bool InExclusiveParallelSection();
bool ParallelTestsShouldPass(JSContext *cx);
void RequestInterruptForForkJoin(JSRuntime *rt, JSRuntime::InterruptMode mode);
bool intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp);
extern const JSJitInfo intrinsic_SetForkJoinTargetRegionInfo;

View File

@ -616,25 +616,14 @@ RegExpShared::execute(JSContext *cx, HandleLinearString input, size_t start,
}
if (result == RegExpRunStatus_Error) {
// The RegExp engine might exit with an exception if an interrupt
// was requested. If this happens, break out and retry the regexp
// in the bytecode interpreter, which can execute while tolerating
// future interrupts. Otherwise, if we keep getting interrupted we
// will never finish executing the regexp.
bool interrupted;
{
JSRuntime::AutoLockForInterrupt lock(cx->runtime());
interrupted = cx->runtime()->interrupt;
}
if (interrupted) {
if (!InvokeInterruptCallback(cx))
return RegExpRunStatus_Error;
break;
}
js_ReportOverRecursed(cx);
return RegExpRunStatus_Error;
// An 'Error' result is returned if a stack overflow guard or
// interrupt guard failed. If CheckOverRecursed doesn't throw, break
// out and retry the regexp in the bytecode interpreter, which can
// execute while tolerating future interrupts. Otherwise, if we keep
// getting interrupted we will never finish executing the regexp.
if (!jit::CheckOverRecursed(cx))
return RegExpRunStatus_Error;
break;
}
if (result == RegExpRunStatus_Success_NotFound)

View File

@ -36,6 +36,7 @@
#include "jit/PcScriptCache.h"
#include "js/MemoryMetrics.h"
#include "js/SliceBudget.h"
#include "vm/Debugger.h"
#include "jscntxtinlines.h"
#include "jsgcinlines.h"
@ -73,7 +74,7 @@ PerThreadData::PerThreadData(JSRuntime *runtime)
runtime_(runtime),
jitTop(nullptr),
jitJSContext(nullptr),
jitStackLimit(0),
jitStackLimit_(0xbad),
#ifdef JS_TRACE_LOGGING
traceLogger(nullptr),
#endif
@ -135,8 +136,8 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime)
),
mainThread(this),
parentRuntime(parentRuntime),
interrupt(false),
interruptPar(false),
interrupt_(false),
interruptPar_(false),
handlingSignal(false),
interruptCallback(nullptr),
interruptLock(nullptr),
@ -155,7 +156,7 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime)
execAlloc_(nullptr),
jitRuntime_(nullptr),
selfHostingGlobal_(nullptr),
nativeStackBase(0),
nativeStackBase(GetNativeStackBase()),
cxCallback(nullptr),
destroyCompartmentCallback(nullptr),
destroyZoneCallback(nullptr),
@ -321,8 +322,6 @@ JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes)
return false;
#endif
nativeStackBase = GetNativeStackBase();
jitSupportsFloatingPoint = js::jit::JitSupportsFloatingPoint();
jitSupportsSimd = js::jit::JitSupportsSimd();
@ -464,17 +463,6 @@ NewObjectCache::clearNurseryObjects(JSRuntime *rt)
#endif
}
void
JSRuntime::resetJitStackLimit()
{
AutoLockForInterrupt lock(this);
mainThread.setJitStackLimit(mainThread.nativeStackLimit[js::StackForUntrustedScript]);
#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
mainThread.setJitStackLimit(js::jit::Simulator::StackLimit());
#endif
}
void
JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *rtSizes)
{
@ -529,33 +517,120 @@ JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Runtim
#endif
}
static bool
InvokeInterruptCallback(JSContext *cx)
{
MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
cx->gcIfNeeded();
// 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.
JSInterruptCallback cb = cx->runtime()->interruptCallback;
if (!cb)
return true;
if (cb(cx)) {
// Debugger treats invoking the interrupt callback as a "step", so
// invoke the onStep handler.
if (cx->compartment()->debugMode()) {
ScriptFrameIter iter(cx);
if (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().start().get();
else
chars = MOZ_UTF16("(stack not available)");
JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_WARNING, js_GetErrorMessage, nullptr,
JSMSG_TERMINATED, chars);
return false;
}
void
PerThreadData::resetJitStackLimit()
{
// Note that, for now, we use the untrusted limit for ion. This is fine,
// because it's the most conservative limit, and if we hit it, we'll bail
// out of ion into the interpeter, which will do a proper recursion check.
#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
jitStackLimit_ = jit::Simulator::StackLimit();
#else
jitStackLimit_ = nativeStackLimit[StackForUntrustedScript];
#endif
}
void
PerThreadData::initJitStackLimit()
{
resetJitStackLimit();
}
void
PerThreadData::initJitStackLimitPar(uintptr_t limit)
{
jitStackLimit_ = limit;
}
void
JSRuntime::requestInterrupt(InterruptMode mode)
{
AutoLockForInterrupt lock(this);
interrupt_ = true;
interruptPar_ = true;
mainThread.jitStackLimit_ = UINTPTR_MAX;
/*
* Invalidate ionTop to trigger its over-recursion check. Note this must be
* set before interrupt, to avoid racing with js::InvokeInterruptCallback,
* into a weird state where interrupt is stuck at 0 but jitStackLimit is
* MAXADDR.
*/
mainThread.setJitStackLimit(-1);
interrupt = true;
RequestInterruptForForkJoin(this, mode);
/*
* asm.js and normal Ion code optionally use memory protection and signal
* handlers to halt running code.
*/
if (canUseSignalHandlers()) {
AutoLockForInterrupt lock(this);
RequestInterruptForAsmJSCode(this, mode);
jit::RequestInterruptForIonCode(this, mode);
}
}
bool
JSRuntime::handleInterrupt(JSContext *cx)
{
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
if (interrupt_ || mainThread.jitStackLimit_ == UINTPTR_MAX) {
interrupt_ = false;
interruptPar_ = false;
mainThread.resetJitStackLimit();
return InvokeInterruptCallback(cx);
}
return true;
}
jit::ExecutableAllocator *
JSRuntime::createExecutableAllocator(JSContext *cx)
{

View File

@ -519,13 +519,20 @@ class PerThreadData : public PerThreadDataFriendFields
*/
JSContext *jitJSContext;
/*
* The stack limit checked by JIT code. This stack limit may be temporarily
* set to null to force JIT code to exit (e.g., for the operation callback).
*/
uintptr_t jitStackLimit;
/* See comment for JSRuntime::interrupt_. */
private:
mozilla::Atomic<uintptr_t, mozilla::Relaxed> jitStackLimit_;
void resetJitStackLimit();
friend struct ::JSRuntime;
public:
void initJitStackLimit();
void initJitStackLimitPar(uintptr_t limit);
inline void setJitStackLimit(uintptr_t limit);
uintptr_t jitStackLimit() const { return jitStackLimit_; }
// For read-only JIT use:
void *addressOfJitStackLimit() { return &jitStackLimit_; }
static size_t offsetOfJitStackLimit() { return offsetof(PerThreadData, jitStackLimit_); }
// Information about the heap allocated backtrack stack used by RegExp JIT code.
irregexp::RegExpStack regexpStack;
@ -678,8 +685,6 @@ class PerThreadData : public PerThreadDataFriendFields
class AutoLockForExclusiveAccess;
void RecomputeStackLimit(JSRuntime *rt, StackKind kind);
} // namespace js
struct JSRuntime : public JS::shadow::Runtime,
@ -703,18 +708,56 @@ struct JSRuntime : public JS::shadow::Runtime,
*/
JSRuntime *parentRuntime;
/*
* If true, we've been asked to call the interrupt callback as soon as
* possible.
*/
mozilla::Atomic<bool, mozilla::Relaxed> interrupt;
private:
mozilla::Atomic<uint32_t, mozilla::Relaxed> interrupt_;
mozilla::Atomic<uint32_t, mozilla::Relaxed> interruptPar_;
public:
/*
* If non-zero, ForkJoin should service an interrupt. This is a separate
* flag from |interrupt| because we cannot use the mprotect trick with PJS
* code and ignore the TriggerCallbackAnyThreadDontStopIon trigger.
*/
mozilla::Atomic<bool, mozilla::Relaxed> interruptPar;
enum InterruptMode {
RequestInterruptMainThread,
RequestInterruptAnyThread,
RequestInterruptAnyThreadDontStopIon,
RequestInterruptAnyThreadForkJoin
};
// Any thread can call requestInterrupt() to request that the main JS thread
// stop running and call the interrupt callback (allowing the interrupt
// callback to halt execution). To stop the main JS thread, requestInterrupt
// sets two fields: interrupt_ (set to true) and jitStackLimit_ (set to
// UINTPTR_MAX). The JS engine must continually poll one of these fields
// and call handleInterrupt if either field has the interrupt value. (The
// point of setting jitStackLimit_ to UINTPTR_MAX is that JIT code already
// needs to guard on jitStackLimit_ in every function prologue to avoid
// stack overflow, so we avoid a second branch on interrupt_ by setting
// jitStackLimit_ to a value that is guaranteed to fail the guard.)
//
// Note that the writes to interrupt_ and jitStackLimit_ use a Relaxed
// Atomic so, while the writes are guaranteed to eventually be visible to
// the main thread, it can happen in any order. handleInterrupt calls the
// interrupt callback if either is set, so it really doesn't matter as long
// as the JS engine is continually polling at least one field. In corner
// cases, this relaxed ordering could lead to an interrupt handler being
// called twice in succession after a single requestInterrupt call, but
// that's fine.
void requestInterrupt(InterruptMode mode);
bool handleInterrupt(JSContext *cx);
MOZ_ALWAYS_INLINE bool hasPendingInterrupt() const {
return interrupt_;
}
MOZ_ALWAYS_INLINE bool hasPendingInterruptPar() const {
return interruptPar_;
}
// For read-only JIT use:
void *addressOfInterruptUint32() {
static_assert(sizeof(interrupt_) == sizeof(uint32_t), "Assumed by JIT callers");
return &interrupt_;
}
void *addressOfInterruptParUint32() {
static_assert(sizeof(interruptPar_) == sizeof(uint32_t), "Assumed by JIT callers");
return &interruptPar_;
}
/* Set when handling a signal for a thread associated with this runtime. */
bool handlingSignal;
@ -900,7 +943,7 @@ struct JSRuntime : public JS::shadow::Runtime,
void setDefaultVersion(JSVersion v) { defaultVersion_ = v; }
/* Base address of the native stack for the current thread. */
uintptr_t nativeStackBase;
const uintptr_t nativeStackBase;
/* The native stack size limit that runtime should not exceed. */
size_t nativeStackQuota[js::StackKindCount];
@ -1266,10 +1309,6 @@ struct JSRuntime : public JS::shadow::Runtime,
bool jitSupportsFloatingPoint;
bool jitSupportsSimd;
// Used to reset stack limit after a signaled interrupt (i.e. jitStackLimit_ = -1)
// has been noticed by Ion/Baseline.
void resetJitStackLimit();
// Cache for jit::GetPcScript().
js::jit::PcScriptCache *ionPcScriptCache;
@ -1330,17 +1369,6 @@ struct JSRuntime : public JS::shadow::Runtime,
/* onOutOfMemory but can call the largeAllocationFailureCallback. */
JS_FRIEND_API(void *) onOutOfMemoryCanGC(void *p, size_t bytes);
// Ways in which the interrupt callback on the runtime can be triggered,
// varying based on which thread is triggering the callback.
enum InterruptMode {
RequestInterruptMainThread,
RequestInterruptAnyThread,
RequestInterruptAnyThreadDontStopIon,
RequestInterruptAnyThreadForkJoin
};
void requestInterrupt(InterruptMode mode);
void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *runtime);
private:
@ -1564,13 +1592,6 @@ class MOZ_STACK_CLASS AutoKeepAtoms
}
};
inline void
PerThreadData::setJitStackLimit(uintptr_t limit)
{
MOZ_ASSERT(runtime_->currentThreadOwnsInterruptLock());
jitStackLimit = limit;
}
inline JSRuntime *
PerThreadData::runtimeFromMainThread()
{