Backed out changeset 10616214c160 (bug 1091916) for Android x86 S4 permafail.

This commit is contained in:
Ryan VanderMeulen 2014-10-31 18:58:42 -04:00
parent 418030317f
commit e9bc83a22c
22 changed files with 290 additions and 239 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -113,17 +113,28 @@ NewGCObject(JSContext *cx, gc::AllocKind allocKind, gc::InitialHeap initialHeap)
bool bool
CheckOverRecursed(JSContext *cx) CheckOverRecursed(JSContext *cx)
{ {
// We just failed the jitStackLimit check. There are two possible reasons: // IonMonkey's stackLimit is equal to nativeStackLimit by default. When we
// - jitStackLimit was the real stack limit and we're over-recursed // request an interrupt, we set the jitStackLimit to nullptr, which causes
// - jitStackLimit was set to UINTPTR_MAX by JSRuntime::requestInterrupt // the stack limit check to fail.
// and we need to call JSRuntime::handleInterrupt. //
// 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.
#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR) #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, 0, return false); JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, 0, return false);
#else #else
JS_CHECK_RECURSION(cx, return false); JS_CHECK_RECURSION(cx, return false);
#endif #endif
gc::MaybeVerifyBarriers(cx);
return cx->runtime()->handleInterrupt(cx); if (cx->runtime()->interrupt)
return InterruptCheck(cx);
return true;
} }
// This function can get called in two contexts. In the usual context, it's // This function can get called in two contexts. In the usual context, it's
@ -167,8 +178,10 @@ CheckOverRecursedWithExtra(JSContext *cx, BaselineFrame *frame,
JS_CHECK_RECURSION_WITH_SP(cx, checkSp, return false); JS_CHECK_RECURSION_WITH_SP(cx, checkSp, return false);
#endif #endif
gc::MaybeVerifyBarriers(cx); if (cx->runtime()->interrupt)
return cx->runtime()->handleInterrupt(cx); return InterruptCheck(cx);
return true;
} }
bool bool

View File

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

View File

@ -2029,48 +2029,68 @@ JS_GetExternalStringFinalizer(JSString *str)
} }
static void static void
SetNativeStackQuotaAndLimit(JSRuntime *rt, StackKind kind, size_t stackSize) SetNativeStackQuota(JSRuntime *rt, StackKind kind, size_t stackSize)
{ {
rt->nativeStackQuota[kind] = 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 JS_STACK_GROWTH_DIRECTION > 0
if (stackSize == 0) { if (stackSize == 0) {
rt->mainThread.nativeStackLimit[kind] = UINTPTR_MAX; rt->mainThread.nativeStackLimit[kind] = UINTPTR_MAX;
} else { } else {
MOZ_ASSERT(rt->nativeStackBase <= size_t(-1) - stackSize); 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 #else
if (stackSize == 0) { if (stackSize == 0) {
rt->mainThread.nativeStackLimit[kind] = 0; rt->mainThread.nativeStackLimit[kind] = 0;
} else { } else {
MOZ_ASSERT(rt->nativeStackBase >= stackSize); MOZ_ASSERT(rt->nativeStackBase >= stackSize);
rt->mainThread.nativeStackLimit[kind] = rt->nativeStackBase - (stackSize - 1); rt->mainThread.nativeStackLimit[kind] =
rt->nativeStackBase - (stackSize - 1);
} }
#endif #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_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) size_t untrustedScriptStackSize)
{ {
MOZ_ASSERT(rt->requestDepth == 0); MOZ_ASSERT_IF(trustedScriptStackSize,
trustedScriptStackSize < systemCodeStackSize);
if (!trustedScriptStackSize) if (!trustedScriptStackSize)
trustedScriptStackSize = systemCodeStackSize; trustedScriptStackSize = systemCodeStackSize;
else MOZ_ASSERT_IF(untrustedScriptStackSize,
MOZ_ASSERT(trustedScriptStackSize < systemCodeStackSize); untrustedScriptStackSize < trustedScriptStackSize);
if (!untrustedScriptStackSize) if (!untrustedScriptStackSize)
untrustedScriptStackSize = trustedScriptStackSize; untrustedScriptStackSize = trustedScriptStackSize;
else SetNativeStackQuota(rt, StackForSystemCode, systemCodeStackSize);
MOZ_ASSERT(untrustedScriptStackSize < trustedScriptStackSize); SetNativeStackQuota(rt, StackForTrustedScript, trustedScriptStackSize);
SetNativeStackQuota(rt, StackForUntrustedScript, untrustedScriptStackSize);
SetNativeStackQuotaAndLimit(rt, StackForSystemCode, systemCodeStackSize);
SetNativeStackQuotaAndLimit(rt, StackForTrustedScript, trustedScriptStackSize);
SetNativeStackQuotaAndLimit(rt, StackForUntrustedScript, untrustedScriptStackSize);
rt->mainThread.initJitStackLimit();
} }
/************************************************************************/ /************************************************************************/

View File

@ -2272,9 +2272,6 @@ JS_GetExternalStringFinalizer(JSString *str);
* The stack quotas for each kind of code should be monotonically descending, * 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 * 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. * 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) extern JS_PUBLIC_API(void)
JS_SetNativeStackQuota(JSRuntime *cx, size_t systemCodeStackSize, JS_SetNativeStackQuota(JSRuntime *cx, size_t systemCodeStackSize,

View File

@ -41,6 +41,7 @@
#include "gc/Marking.h" #include "gc/Marking.h"
#include "jit/Ion.h" #include "jit/Ion.h"
#include "js/CharacterEncoding.h" #include "js/CharacterEncoding.h"
#include "vm/Debugger.h"
#include "vm/HelperThreads.h" #include "vm/HelperThreads.h"
#include "vm/Shape.h" #include "vm/Shape.h"
@ -970,6 +971,90 @@ js_GetErrorMessage(void *userRef, const unsigned errorNumber)
return nullptr; 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) ThreadSafeContext::ThreadSafeContext(JSRuntime *rt, PerThreadData *pt, ContextKind kind)
: ContextFriendFields(rt), : ContextFriendFields(rt),
contextKind_(kind), contextKind_(kind),

View File

@ -289,7 +289,7 @@ struct ThreadSafeContext : ContextFriendFields,
PropertyName *emptyString() { return runtime_->emptyString; } PropertyName *emptyString() { return runtime_->emptyString; }
FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); } FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); }
void *runtimeAddressForJit() { return runtime_; } void *runtimeAddressForJit() { return runtime_; }
void *runtimeAddressOfInterruptUint32() { return runtime_->addressOfInterruptUint32(); } void *runtimeAddressOfInterrupt() { return &runtime_->interrupt; }
void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; } void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; }
void *stackLimitAddressForJitCode(StackKind kind); void *stackLimitAddressForJitCode(StackKind kind);
size_t gcSystemPageSize() { return gc::SystemPageSize(); } size_t gcSystemPageSize() { return gc::SystemPageSize(); }
@ -782,15 +782,33 @@ extern const JSErrorFormatString js_ErrorFormatString[JSErr_Limit];
namespace js { 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 MOZ_ALWAYS_INLINE bool
CheckForInterrupt(JSContext *cx) CheckForInterrupt(JSContext *cx)
{ {
// Add an inline fast-path since we have to check for interrupts in some hot MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
// C++ loops of library builtins. return !cx->runtime()->interrupt || InvokeInterruptCallback(cx);
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(); rt->gc.runDebugGC();
#endif #endif
if (rt->hasPendingInterrupt()) { if (rt->interrupt) {
// Invoking the interrupt callback can fail and we can't usefully // Invoking the interrupt callback can fail and we can't usefully
// handle that here. Just check in case we need to collect instead. // handle that here. Just check in case we need to collect instead.
ncx->gcIfNeeded(); ncx->gcIfNeeded();

View File

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

View File

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

View File

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

View File

@ -621,8 +621,14 @@ RegExpShared::execute(JSContext *cx, HandleLinearString input, size_t start,
// in the bytecode interpreter, which can execute while tolerating // in the bytecode interpreter, which can execute while tolerating
// future interrupts. Otherwise, if we keep getting interrupted we // future interrupts. Otherwise, if we keep getting interrupted we
// will never finish executing the regexp. // will never finish executing the regexp.
if (cx->runtime()->hasPendingInterrupt()) { bool interrupted;
if (!cx->runtime()->handleInterrupt(cx)) {
JSRuntime::AutoLockForInterrupt lock(cx->runtime());
interrupted = cx->runtime()->interrupt;
}
if (interrupted) {
if (!InvokeInterruptCallback(cx))
return RegExpRunStatus_Error; return RegExpRunStatus_Error;
break; break;
} }

View File

@ -36,7 +36,6 @@
#include "jit/PcScriptCache.h" #include "jit/PcScriptCache.h"
#include "js/MemoryMetrics.h" #include "js/MemoryMetrics.h"
#include "js/SliceBudget.h" #include "js/SliceBudget.h"
#include "vm/Debugger.h"
#include "jscntxtinlines.h" #include "jscntxtinlines.h"
#include "jsgcinlines.h" #include "jsgcinlines.h"
@ -74,7 +73,7 @@ PerThreadData::PerThreadData(JSRuntime *runtime)
runtime_(runtime), runtime_(runtime),
jitTop(nullptr), jitTop(nullptr),
jitJSContext(nullptr), jitJSContext(nullptr),
jitStackLimit_(0xbad), jitStackLimit(0),
#ifdef JS_TRACE_LOGGING #ifdef JS_TRACE_LOGGING
traceLogger(nullptr), traceLogger(nullptr),
#endif #endif
@ -136,8 +135,8 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime)
), ),
mainThread(this), mainThread(this),
parentRuntime(parentRuntime), parentRuntime(parentRuntime),
interrupt_(false), interrupt(false),
interruptPar_(false), interruptPar(false),
handlingSignal(false), handlingSignal(false),
interruptCallback(nullptr), interruptCallback(nullptr),
interruptLock(nullptr), interruptLock(nullptr),
@ -157,7 +156,7 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime)
execAlloc_(nullptr), execAlloc_(nullptr),
jitRuntime_(nullptr), jitRuntime_(nullptr),
selfHostingGlobal_(nullptr), selfHostingGlobal_(nullptr),
nativeStackBase(GetNativeStackBase()), nativeStackBase(0),
cxCallback(nullptr), cxCallback(nullptr),
destroyCompartmentCallback(nullptr), destroyCompartmentCallback(nullptr),
destroyZoneCallback(nullptr), destroyZoneCallback(nullptr),
@ -323,6 +322,8 @@ JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes)
return false; return false;
#endif #endif
nativeStackBase = GetNativeStackBase();
jitSupportsFloatingPoint = js::jit::JitSupportsFloatingPoint(); jitSupportsFloatingPoint = js::jit::JitSupportsFloatingPoint();
jitSupportsSimd = js::jit::JitSupportsSimd(); jitSupportsSimd = js::jit::JitSupportsSimd();
@ -464,6 +465,17 @@ NewObjectCache::clearNurseryObjects(JSRuntime *rt)
#endif #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 void
JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *rtSizes) JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *rtSizes)
{ {
@ -518,120 +530,33 @@ JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Runtim
#endif #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 void
JSRuntime::requestInterrupt(InterruptMode mode) JSRuntime::requestInterrupt(InterruptMode mode)
{ {
interrupt_ = true; AutoLockForInterrupt lock(this);
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()) { if (canUseSignalHandlers()) {
AutoLockForInterrupt lock(this);
RequestInterruptForAsmJSCode(this, mode); RequestInterruptForAsmJSCode(this, mode);
jit::RequestInterruptForIonCode(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 * jit::ExecutableAllocator *
JSRuntime::createExecutableAllocator(JSContext *cx) JSRuntime::createExecutableAllocator(JSContext *cx)
{ {

View File

@ -519,20 +519,13 @@ class PerThreadData : public PerThreadDataFriendFields
*/ */
JSContext *jitJSContext; JSContext *jitJSContext;
/* See comment for JSRuntime::interrupt_. */ /*
private: * The stack limit checked by JIT code. This stack limit may be temporarily
mozilla::Atomic<uintptr_t, mozilla::Relaxed> jitStackLimit_; * set to null to force JIT code to exit (e.g., for the operation callback).
void resetJitStackLimit(); */
friend struct ::JSRuntime; uintptr_t jitStackLimit;
public:
void initJitStackLimit();
void initJitStackLimitPar(uintptr_t limit);
uintptr_t jitStackLimit() const { return jitStackLimit_; } inline void setJitStackLimit(uintptr_t limit);
// 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. // Information about the heap allocated backtrack stack used by RegExp JIT code.
irregexp::RegExpStack regexpStack; irregexp::RegExpStack regexpStack;
@ -685,6 +678,8 @@ class PerThreadData : public PerThreadDataFriendFields
class AutoLockForExclusiveAccess; class AutoLockForExclusiveAccess;
void RecomputeStackLimit(JSRuntime *rt, StackKind kind);
} // namespace js } // namespace js
struct JSRuntime : public JS::shadow::Runtime, struct JSRuntime : public JS::shadow::Runtime,
@ -708,56 +703,18 @@ struct JSRuntime : public JS::shadow::Runtime,
*/ */
JSRuntime *parentRuntime; JSRuntime *parentRuntime;
private: /*
mozilla::Atomic<uint32_t, mozilla::Relaxed> interrupt_; * If true, we've been asked to call the interrupt callback as soon as
mozilla::Atomic<uint32_t, mozilla::Relaxed> interruptPar_; * possible.
public: */
mozilla::Atomic<bool, mozilla::Relaxed> interrupt;
enum InterruptMode { /*
RequestInterruptMainThread, * If non-zero, ForkJoin should service an interrupt. This is a separate
RequestInterruptAnyThread, * flag from |interrupt| because we cannot use the mprotect trick with PJS
RequestInterruptAnyThreadDontStopIon, * code and ignore the TriggerCallbackAnyThreadDontStopIon trigger.
RequestInterruptAnyThreadForkJoin */
}; mozilla::Atomic<bool, mozilla::Relaxed> interruptPar;
// 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. */ /* Set when handling a signal for a thread associated with this runtime. */
bool handlingSignal; bool handlingSignal;
@ -949,7 +906,7 @@ struct JSRuntime : public JS::shadow::Runtime,
void setDefaultVersion(JSVersion v) { defaultVersion_ = v; } void setDefaultVersion(JSVersion v) { defaultVersion_ = v; }
/* Base address of the native stack for the current thread. */ /* Base address of the native stack for the current thread. */
const uintptr_t nativeStackBase; uintptr_t nativeStackBase;
/* The native stack size limit that runtime should not exceed. */ /* The native stack size limit that runtime should not exceed. */
size_t nativeStackQuota[js::StackKindCount]; size_t nativeStackQuota[js::StackKindCount];
@ -1315,6 +1272,10 @@ struct JSRuntime : public JS::shadow::Runtime,
bool jitSupportsFloatingPoint; bool jitSupportsFloatingPoint;
bool jitSupportsSimd; 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(). // Cache for jit::GetPcScript().
js::jit::PcScriptCache *ionPcScriptCache; js::jit::PcScriptCache *ionPcScriptCache;
@ -1375,6 +1336,17 @@ struct JSRuntime : public JS::shadow::Runtime,
/* onOutOfMemory but can call the largeAllocationFailureCallback. */ /* onOutOfMemory but can call the largeAllocationFailureCallback. */
JS_FRIEND_API(void *) onOutOfMemoryCanGC(void *p, size_t bytes); 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); void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *runtime);
private: private:
@ -1598,6 +1570,13 @@ class MOZ_STACK_CLASS AutoKeepAtoms
} }
}; };
inline void
PerThreadData::setJitStackLimit(uintptr_t limit)
{
MOZ_ASSERT(runtime_->currentThreadOwnsInterruptLock());
jitStackLimit = limit;
}
inline JSRuntime * inline JSRuntime *
PerThreadData::runtimeFromMainThread() PerThreadData::runtimeFromMainThread()
{ {