From 1533f5ace36044b84785d09f3a1fccd698d7e213 Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Tue, 11 Nov 2014 14:53:11 -0500 Subject: [PATCH] Backed out changeset 583284c299fb (bug 1091916) for causing intermittent "too much recursion" jit-test failures on Win32 and OSX 10.6. CLOSED TREE --- js/src/asmjs/AsmJSModule.cpp | 6 +- .../irregexp/NativeRegExpMacroAssembler.cpp | 4 +- js/src/jit/BaselineCompiler.cpp | 4 +- js/src/jit/CodeGenerator.cpp | 8 +- js/src/jit/CompileWrappers.cpp | 10 +- js/src/jit/CompileWrappers.h | 4 +- js/src/jit/Ion.cpp | 4 +- js/src/jit/IonMacroAssembler.cpp | 2 +- js/src/jit/ParallelFunctions.cpp | 2 +- js/src/jit/VMFunctions.cpp | 29 +++- js/src/jit/shared/Assembler-shared.h | 2 +- js/src/jsapi.cpp | 54 ++++--- js/src/jsapi.h | 3 - js/src/jscntxt.cpp | 85 +++++++++++ js/src/jscntxt.h | 32 +++- js/src/jsgcinlines.h | 2 +- js/src/jsnativestack.h | 1 - js/src/vm/ForkJoin.cpp | 17 ++- js/src/vm/ForkJoin.h | 2 + js/src/vm/RegExpObject.cpp | 10 +- js/src/vm/Runtime.cpp | 143 +++++------------- js/src/vm/Runtime.h | 105 +++++-------- 22 files changed, 290 insertions(+), 239 deletions(-) diff --git a/js/src/asmjs/AsmJSModule.cpp b/js/src/asmjs/AsmJSModule.cpp index ae86f8fb05c6..bdceba091af9 100644 --- a/js/src/asmjs/AsmJSModule.cpp +++ b/js/src/asmjs/AsmJSModule.cpp @@ -500,7 +500,7 @@ AsmJSHandleExecutionInterrupt() { AsmJSActivation *act = PerThreadData::innermostAsmJSActivation(); act->module().setInterrupted(true); - bool ret = CheckForInterrupt(act->cx()); + bool ret = HandleExecutionInterrupt(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_RuntimeInterruptUint32: - return cx->runtimeAddressOfInterruptUint32(); + case AsmJSImm_RuntimeInterrupt: + return cx->runtimeAddressOfInterrupt(); case AsmJSImm_StackLimit: return cx->stackLimitAddressForJitCode(StackForUntrustedScript); case AsmJSImm_ReportOverRecursed: diff --git a/js/src/irregexp/NativeRegExpMacroAssembler.cpp b/js/src/irregexp/NativeRegExpMacroAssembler.cpp index ea6a71ab3336..8cce4ee744b2 100644 --- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp +++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp @@ -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.addressOfJitStackLimit(); + void *stack_limit = &runtime->mainThread.jitStackLimit; 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->addressOfInterruptUint32()), Imm32(0), + AbsoluteAddress(&runtime->interrupt), Imm32(0), &noInterrupt); masm.movePtr(ImmWord(RegExpRunStatus_Error), temp0); masm.jump(&exit_label_); diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index a833129a0e4e..5f3cbc8cd0f8 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -500,7 +500,7 @@ bool BaselineCompiler::emitStackCheck(bool earlyCheck) { Label skipCall; - void *limitAddr = cx->runtime()->mainThread.addressOfJitStackLimit(); + uintptr_t *limitAddr = &cx->runtime()->mainThread.jitStackLimit; 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 = cx->runtimeAddressOfInterruptUint32(); + void *interrupt = (void *)&cx->runtime()->interrupt; masm.branch32(Assembler::Equal, AbsoluteAddress(interrupt), Imm32(0), &done); prepareVMCall(); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index be05db38ac57..b9b45e213d00 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -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, PerThreadData::offsetOfJitStackLimit()), tempReg); + masm.loadPtr(Address(tempReg, offsetof(PerThreadData, jitStackLimit)), 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->addressOfInterruptUint32()); + AbsoluteAddress interruptAddr(GetIonContext()->runtime->addressOfInterrupt()); 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_RuntimeInterruptUint32), scratch); - masm.load32(Address(scratch, 0), scratch); + masm.movePtr(AsmJSImmPtr(AsmJSImm_RuntimeInterrupt), scratch); + masm.load8ZeroExtend(Address(scratch, 0), scratch); Label rejoin; masm.branch32(Assembler::Equal, scratch, Imm32(0), &rejoin); { diff --git a/js/src/jit/CompileWrappers.cpp b/js/src/jit/CompileWrappers.cpp index 210eaf97c563..0030b92b26d8 100644 --- a/js/src/jit/CompileWrappers.cpp +++ b/js/src/jit/CompileWrappers.cpp @@ -43,7 +43,7 @@ CompileRuntime::addressOfJitTop() const void * CompileRuntime::addressOfJitStackLimit() { - return runtime()->mainThread.addressOfJitStackLimit(); + return &runtime()->mainThread.jitStackLimit; } const void * @@ -73,15 +73,15 @@ CompileRuntime::addressOfGCZeal() #endif const void * -CompileRuntime::addressOfInterruptUint32() +CompileRuntime::addressOfInterrupt() { - return runtime()->addressOfInterruptUint32(); + return &runtime()->interrupt; } const void * -CompileRuntime::addressOfInterruptParUint32() +CompileRuntime::addressOfInterruptPar() { - return runtime()->addressOfInterruptParUint32(); + return &runtime()->interruptPar; } const void * diff --git a/js/src/jit/CompileWrappers.h b/js/src/jit/CompileWrappers.h index 879bca9e8f45..fe896315d0ed 100644 --- a/js/src/jit/CompileWrappers.h +++ b/js/src/jit/CompileWrappers.h @@ -50,8 +50,8 @@ class CompileRuntime const void *addressOfGCZeal(); #endif - const void *addressOfInterruptUint32(); - const void *addressOfInterruptParUint32(); + const void *addressOfInterrupt(); + const void *addressOfInterruptPar(); const void *addressOfThreadPool(); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 0ea6001111d8..f6075bfa8246 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -428,7 +428,7 @@ JitRuntime::ensureIonCodeAccessible(JSRuntime *rt) ionCodeProtected_ = false; } - if (rt->hasPendingInterrupt()) { + if (rt->interrupt) { // 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()->hasPendingInterrupt()) + if (cx->runtime()->interrupt) PatchBackedge(backedge, interruptCheck, JitRuntime::BackedgeInterruptCheck); else PatchBackedge(backedge, loopHeader, JitRuntime::BackedgeLoopHeader); diff --git a/js/src/jit/IonMacroAssembler.cpp b/js/src/jit/IonMacroAssembler.cpp index 086f28071807..11b8701b38fe 100644 --- a/js/src/jit/IonMacroAssembler.cpp +++ b/js/src/jit/IonMacroAssembler.cpp @@ -1238,7 +1238,7 @@ MacroAssembler::loadStringChar(Register str, Register index, Register output) void 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); } diff --git a/js/src/jit/ParallelFunctions.cpp b/js/src/jit/ParallelFunctions.cpp index 58df34db82cc..2080bb959ad4 100644 --- a/js/src/jit/ParallelFunctions.cpp +++ b/js/src/jit/ParallelFunctions.cpp @@ -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; } diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 501562c5b663..bc798f6f1dda 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -113,17 +113,28 @@ NewGCObject(JSContext *cx, gc::AllocKind allocKind, gc::InitialHeap initialHeap) bool CheckOverRecursed(JSContext *cx) { - // 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. + // 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. #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 - 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 @@ -167,8 +178,10 @@ CheckOverRecursedWithExtra(JSContext *cx, BaselineFrame *frame, JS_CHECK_RECURSION_WITH_SP(cx, checkSp, return false); #endif - gc::MaybeVerifyBarriers(cx); - return cx->runtime()->handleInterrupt(cx); + if (cx->runtime()->interrupt) + return InterruptCheck(cx); + + return true; } bool diff --git a/js/src/jit/shared/Assembler-shared.h b/js/src/jit/shared/Assembler-shared.h index 9b54186df22e..2144df9b0be3 100644 --- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -783,7 +783,7 @@ enum AsmJSImmKind AsmJSImm_PowD = AsmJSExit::Builtin_PowD, AsmJSImm_ATan2D = AsmJSExit::Builtin_ATan2D, AsmJSImm_Runtime, - AsmJSImm_RuntimeInterruptUint32, + AsmJSImm_RuntimeInterrupt, AsmJSImm_StackLimit, AsmJSImm_ReportOverRecursed, AsmJSImm_OnDetached, diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index a913b3d7958a..0bec32dca66e 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -2031,48 +2031,68 @@ JS_GetExternalStringFinalizer(JSString *str) } static void -SetNativeStackQuotaAndLimit(JSRuntime *rt, StackKind kind, size_t stackSize) +SetNativeStackQuota(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(rt->requestDepth == 0); - + MOZ_ASSERT_IF(trustedScriptStackSize, + trustedScriptStackSize < systemCodeStackSize); if (!trustedScriptStackSize) trustedScriptStackSize = systemCodeStackSize; - else - MOZ_ASSERT(trustedScriptStackSize < systemCodeStackSize); - + MOZ_ASSERT_IF(untrustedScriptStackSize, + untrustedScriptStackSize < trustedScriptStackSize); if (!untrustedScriptStackSize) untrustedScriptStackSize = trustedScriptStackSize; - else - MOZ_ASSERT(untrustedScriptStackSize < trustedScriptStackSize); - - SetNativeStackQuotaAndLimit(rt, StackForSystemCode, systemCodeStackSize); - SetNativeStackQuotaAndLimit(rt, StackForTrustedScript, trustedScriptStackSize); - SetNativeStackQuotaAndLimit(rt, StackForUntrustedScript, untrustedScriptStackSize); - - rt->mainThread.initJitStackLimit(); + SetNativeStackQuota(rt, StackForSystemCode, systemCodeStackSize); + SetNativeStackQuota(rt, StackForTrustedScript, trustedScriptStackSize); + SetNativeStackQuota(rt, StackForUntrustedScript, untrustedScriptStackSize); } /************************************************************************/ diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 3946eec97cdc..0b434a3ac58a 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2272,9 +2272,6 @@ 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, diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 412ae616d270..cb861567edfe 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -41,6 +41,7 @@ #include "gc/Marking.h" #include "jit/Ion.h" #include "js/CharacterEncoding.h" +#include "vm/Debugger.h" #include "vm/HelperThreads.h" #include "vm/Shape.h" @@ -970,6 +971,90 @@ 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), diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 4c81f3febbd0..3fa252c061a7 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -289,7 +289,7 @@ struct ThreadSafeContext : ContextFriendFields, PropertyName *emptyString() { return runtime_->emptyString; } FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); } void *runtimeAddressForJit() { return runtime_; } - void *runtimeAddressOfInterruptUint32() { return runtime_->addressOfInterruptUint32(); } + void *runtimeAddressOfInterrupt() { return &runtime_->interrupt; } void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; } void *stackLimitAddressForJitCode(StackKind kind); size_t gcSystemPageSize() { return gc::SystemPageSize(); } @@ -782,15 +782,33 @@ 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) { - // 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; + MOZ_ASSERT(cx->runtime()->requestDepth >= 1); + return !cx->runtime()->interrupt || InvokeInterruptCallback(cx); } /************************************************************************/ diff --git a/js/src/jsgcinlines.h b/js/src/jsgcinlines.h index bf4ec955c7fd..1921c6b8b776 100644 --- a/js/src/jsgcinlines.h +++ b/js/src/jsgcinlines.h @@ -526,7 +526,7 @@ CheckAllocatorState(ThreadSafeContext *cx, AllocKind kind) rt->gc.runDebugGC(); #endif - if (rt->hasPendingInterrupt()) { + if (rt->interrupt) { // 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(); diff --git a/js/src/jsnativestack.h b/js/src/jsnativestack.h index bcfe0ad88d27..86abcd08ac5c 100644 --- a/js/src/jsnativestack.h +++ b/js/src/jsnativestack.h @@ -18,7 +18,6 @@ inline uintptr_t GetNativeStackBase() { uintptr_t stackBase = reinterpret_cast(GetNativeStackBaseImpl()); - MOZ_ASSERT(stackBase != 0); MOZ_ASSERT(stackBase % sizeof(void *) == 0); return stackBase; } diff --git a/js/src/vm/ForkJoin.cpp b/js/src/vm/ForkJoin.cpp index 7e1471311716..dd828fd771a4 100644 --- a/js/src/vm/ForkJoin.cpp +++ b/js/src/vm/ForkJoin.cpp @@ -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()->hasPendingInterruptPar()) + if (cx_->runtime()->interruptPar) 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.initJitStackLimitPar(stackLimit); + thisThread.jitStackLimit = 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.initJitStackLimitPar(GetNativeStackLimit(cx_)); + thisThread.jitStackLimit = 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()->hasPendingInterruptPar()); + MOZ_ASSERT(cx_->runtime()->interruptPar); // 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()->hasPendingInterruptPar()) { + if (runtime()->interruptPar) { shared_->setAbortFlagDueToInterrupt(*this); return false; } @@ -2273,6 +2273,13 @@ 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) { diff --git a/js/src/vm/ForkJoin.h b/js/src/vm/ForkJoin.h index 8554d4f87f7d..55af6f09f7a1 100644 --- a/js/src/vm/ForkJoin.h +++ b/js/src/vm/ForkJoin.h @@ -546,6 +546,8 @@ 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; diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 6429469b750e..6e2ac67120dc 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -621,8 +621,14 @@ RegExpShared::execute(JSContext *cx, HandleLinearString input, size_t start, // 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 (cx->runtime()->hasPendingInterrupt()) { - if (!cx->runtime()->handleInterrupt(cx)) + bool interrupted; + { + JSRuntime::AutoLockForInterrupt lock(cx->runtime()); + interrupted = cx->runtime()->interrupt; + } + + if (interrupted) { + if (!InvokeInterruptCallback(cx)) return RegExpRunStatus_Error; break; } diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 44b5516380e5..105f3eccace9 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -36,7 +36,6 @@ #include "jit/PcScriptCache.h" #include "js/MemoryMetrics.h" #include "js/SliceBudget.h" -#include "vm/Debugger.h" #include "jscntxtinlines.h" #include "jsgcinlines.h" @@ -74,7 +73,7 @@ PerThreadData::PerThreadData(JSRuntime *runtime) runtime_(runtime), jitTop(nullptr), jitJSContext(nullptr), - jitStackLimit_(0xbad), + jitStackLimit(0), #ifdef JS_TRACE_LOGGING traceLogger(nullptr), #endif @@ -136,8 +135,8 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime) ), mainThread(this), parentRuntime(parentRuntime), - interrupt_(false), - interruptPar_(false), + interrupt(false), + interruptPar(false), handlingSignal(false), interruptCallback(nullptr), interruptLock(nullptr), @@ -156,7 +155,7 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime) execAlloc_(nullptr), jitRuntime_(nullptr), selfHostingGlobal_(nullptr), - nativeStackBase(GetNativeStackBase()), + nativeStackBase(0), cxCallback(nullptr), destroyCompartmentCallback(nullptr), destroyZoneCallback(nullptr), @@ -322,6 +321,8 @@ JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes) return false; #endif + nativeStackBase = GetNativeStackBase(); + jitSupportsFloatingPoint = js::jit::JitSupportsFloatingPoint(); jitSupportsSimd = js::jit::JitSupportsSimd(); @@ -463,6 +464,17 @@ 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) { @@ -517,120 +529,33 @@ 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) { - interrupt_ = true; - interruptPar_ = true; - mainThread.jitStackLimit_ = UINTPTR_MAX; + AutoLockForInterrupt lock(this); + /* + * 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) { diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 3735e3140fec..7c883a686db7 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -519,20 +519,13 @@ class PerThreadData : public PerThreadDataFriendFields */ JSContext *jitJSContext; - /* See comment for JSRuntime::interrupt_. */ - private: - mozilla::Atomic jitStackLimit_; - void resetJitStackLimit(); - friend struct ::JSRuntime; - public: - void initJitStackLimit(); - void initJitStackLimitPar(uintptr_t limit); + /* + * 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; - uintptr_t jitStackLimit() const { return jitStackLimit_; } - - // For read-only JIT use: - void *addressOfJitStackLimit() { return &jitStackLimit_; } - static size_t offsetOfJitStackLimit() { return offsetof(PerThreadData, jitStackLimit_); } + inline void setJitStackLimit(uintptr_t limit); // Information about the heap allocated backtrack stack used by RegExp JIT code. irregexp::RegExpStack regexpStack; @@ -685,6 +678,8 @@ class PerThreadData : public PerThreadDataFriendFields class AutoLockForExclusiveAccess; +void RecomputeStackLimit(JSRuntime *rt, StackKind kind); + } // namespace js struct JSRuntime : public JS::shadow::Runtime, @@ -708,56 +703,18 @@ struct JSRuntime : public JS::shadow::Runtime, */ JSRuntime *parentRuntime; - private: - mozilla::Atomic interrupt_; - mozilla::Atomic interruptPar_; - public: + /* + * If true, we've been asked to call the interrupt callback as soon as + * possible. + */ + mozilla::Atomic interrupt; - 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_; - } + /* + * 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 interruptPar; /* Set when handling a signal for a thread associated with this runtime. */ bool handlingSignal; @@ -943,7 +900,7 @@ struct JSRuntime : public JS::shadow::Runtime, void setDefaultVersion(JSVersion v) { defaultVersion_ = v; } /* 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. */ size_t nativeStackQuota[js::StackKindCount]; @@ -1309,6 +1266,10 @@ 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; @@ -1369,6 +1330,17 @@ 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: @@ -1592,6 +1564,13 @@ class MOZ_STACK_CLASS AutoKeepAtoms } }; +inline void +PerThreadData::setJitStackLimit(uintptr_t limit) +{ + MOZ_ASSERT(runtime_->currentThreadOwnsInterruptLock()); + jitStackLimit = limit; +} + inline JSRuntime * PerThreadData::runtimeFromMainThread() {