mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
Bug 1091916
- simplify the interrupt/jitStackLimit situation (r=bhackett)
--HG-- extra : rebase_source : 44ac5dbc77174414f4596aa7c318fdaa23b8e292
This commit is contained in:
parent
50868f204d
commit
0bbf234774
@ -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:
|
||||
|
@ -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_);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
{
|
||||
|
@ -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 *
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user