/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "jit/Bailouts.h" #include "mozilla/ScopeExit.h" #include "jscntxt.h" #include "jit/BaselineJIT.h" #include "jit/Ion.h" #include "jit/JitCompartment.h" #include "jit/JitSpewer.h" #include "jit/Snapshots.h" #include "vm/TraceLogging.h" #include "jit/JitFrameIterator-inl.h" #include "vm/Probes-inl.h" #include "vm/Stack-inl.h" using namespace js; using namespace js::jit; using mozilla::IsInRange; uint32_t jit::Bailout(BailoutStack* sp, BaselineBailoutInfo** bailoutInfo) { JSContext* cx = GetJSContextFromMainThread(); MOZ_ASSERT(bailoutInfo); // We don't have an exit frame. MOZ_ASSERT(IsInRange(FAKE_JIT_TOP_FOR_BAILOUT, 0, 0x1000) && IsInRange(FAKE_JIT_TOP_FOR_BAILOUT + sizeof(CommonFrameLayout), 0, 0x1000), "Fake jitTop pointer should be within the first page."); cx->runtime()->jitTop = FAKE_JIT_TOP_FOR_BAILOUT; JitActivationIterator jitActivations(cx->runtime()); BailoutFrameInfo bailoutData(jitActivations, sp); JitFrameIterator iter(jitActivations); MOZ_ASSERT(!iter.ionScript()->invalidated()); CommonFrameLayout* currentFramePtr = iter.current(); TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); TraceLogTimestamp(logger, TraceLogger_Bailout); JitSpew(JitSpew_IonBailouts, "Took bailout! Snapshot offset: %d", iter.snapshotOffset()); MOZ_ASSERT(IsBaselineEnabled(cx)); *bailoutInfo = nullptr; uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, false, bailoutInfo, /* excInfo = */ nullptr); MOZ_ASSERT(retval == BAILOUT_RETURN_OK || retval == BAILOUT_RETURN_FATAL_ERROR || retval == BAILOUT_RETURN_OVERRECURSED); MOZ_ASSERT_IF(retval == BAILOUT_RETURN_OK, *bailoutInfo != nullptr); if (retval != BAILOUT_RETURN_OK) { JSScript* script = iter.script(); probes::ExitScript(cx, script, script->functionNonDelazifying(), /* popSPSFrame = */ false); } // This condition was wrong when we entered this bailout function, but it // might be true now. A GC might have reclaimed all the Jit code and // invalidated all frames which are currently on the stack. As we are // already in a bailout, we could not switch to an invalidation // bailout. When the code of an IonScript which is on the stack is // invalidated (see InvalidateActivation), we remove references to it and // increment the reference counter for each activation that appear on the // stack. As the bailed frame is one of them, we have to decrement it now. if (iter.ionScript()->invalidated()) iter.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp()); // NB: Commentary on how |lastProfilingFrame| is set from bailouts. // // Once we return to jitcode, any following frames might get clobbered, // but the current frame will not (as it will be clobbered "in-place" // with a baseline frame that will share the same frame prefix). // However, there may be multiple baseline frames unpacked from this // single Ion frame, which means we will need to once again reset // |lastProfilingFrame| to point to the correct unpacked last frame // in |FinishBailoutToBaseline|. // // In the case of error, the jitcode will jump immediately to an // exception handler, which will unwind the frames and properly set // the |lastProfilingFrame| to point to the frame being resumed into // (see |AutoResetLastProfilerFrameOnReturnFromException|). // // In both cases, we want to temporarily set the |lastProfilingFrame| // to the current frame being bailed out, and then fix it up later. if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) cx->runtime()->jitActivation->setLastProfilingFrame(currentFramePtr); return retval; } uint32_t jit::InvalidationBailout(InvalidationBailoutStack* sp, size_t* frameSizeOut, BaselineBailoutInfo** bailoutInfo) { sp->checkInvariants(); JSContext* cx = GetJSContextFromMainThread(); // We don't have an exit frame. cx->runtime()->jitTop = FAKE_JIT_TOP_FOR_BAILOUT; JitActivationIterator jitActivations(cx->runtime()); BailoutFrameInfo bailoutData(jitActivations, sp); JitFrameIterator iter(jitActivations); CommonFrameLayout* currentFramePtr = iter.current(); TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); TraceLogTimestamp(logger, TraceLogger_Invalidation); JitSpew(JitSpew_IonBailouts, "Took invalidation bailout! Snapshot offset: %d", iter.snapshotOffset()); // Note: the frame size must be computed before we return from this function. *frameSizeOut = iter.frameSize(); MOZ_ASSERT(IsBaselineEnabled(cx)); *bailoutInfo = nullptr; uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, true, bailoutInfo, /* excInfo = */ nullptr); MOZ_ASSERT(retval == BAILOUT_RETURN_OK || retval == BAILOUT_RETURN_FATAL_ERROR || retval == BAILOUT_RETURN_OVERRECURSED); MOZ_ASSERT_IF(retval == BAILOUT_RETURN_OK, *bailoutInfo != nullptr); if (retval != BAILOUT_RETURN_OK) { // If the bailout failed, then bailout trampoline will pop the // current frame and jump straight to exception handling code when // this function returns. Any SPS entry pushed for this frame will // be silently forgotten. // // We call ExitScript here to ensure that if the ionScript had SPS // instrumentation, then the SPS entry for it is popped. // // However, if the bailout was during argument check, then a // pseudostack frame would not have been pushed in the first // place, so don't pop anything in that case. JSScript* script = iter.script(); probes::ExitScript(cx, script, script->functionNonDelazifying(), /* popSPSFrame = */ false); #ifdef JS_JITSPEW JitFrameLayout* frame = iter.jsFrame(); JitSpew(JitSpew_IonInvalidate, "Bailout failed (%s)", (retval == BAILOUT_RETURN_FATAL_ERROR) ? "Fatal Error" : "Over Recursion"); JitSpew(JitSpew_IonInvalidate, " calleeToken %p", (void*) frame->calleeToken()); JitSpew(JitSpew_IonInvalidate, " frameSize %u", unsigned(frame->prevFrameLocalSize())); JitSpew(JitSpew_IonInvalidate, " ra %p", (void*) frame->returnAddress()); #endif } iter.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp()); // Make the frame being bailed out the top profiled frame. if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) cx->runtime()->jitActivation->setLastProfilingFrame(currentFramePtr); return retval; } BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations, const JitFrameIterator& frame) : machine_(frame.machineState()) { framePointer_ = (uint8_t*) frame.fp(); topFrameSize_ = frame.frameSize(); topIonScript_ = frame.ionScript(); attachOnJitActivation(activations); const OsiIndex* osiIndex = frame.osiIndex(); snapshotOffset_ = osiIndex->snapshotOffset(); } uint32_t jit::ExceptionHandlerBailout(JSContext* cx, const InlineFrameIterator& frame, ResumeFromException* rfe, const ExceptionBailoutInfo& excInfo, bool* overrecursed) { // We can be propagating debug mode exceptions without there being an // actual exception pending. For instance, when we return false from an // operation callback like a timeout handler. MOZ_ASSERT_IF(!excInfo.propagatingIonExceptionForDebugMode(), cx->isExceptionPending()); uint8_t* prevJitTop = cx->runtime()->jitTop; auto restoreJitTop = mozilla::MakeScopeExit([&]() { cx->runtime()->jitTop = prevJitTop; }); cx->runtime()->jitTop = FAKE_JIT_TOP_FOR_BAILOUT; gc::AutoSuppressGC suppress(cx); JitActivationIterator jitActivations(cx->runtime()); BailoutFrameInfo bailoutData(jitActivations, frame.frame()); JitFrameIterator iter(jitActivations); CommonFrameLayout* currentFramePtr = iter.current(); BaselineBailoutInfo* bailoutInfo = nullptr; uint32_t retval; { // Currently we do not tolerate OOM here so as not to complicate the // exception handling code further. AutoEnterOOMUnsafeRegion oomUnsafe; retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, true, &bailoutInfo, &excInfo); if (retval == BAILOUT_RETURN_FATAL_ERROR && cx->isThrowingOutOfMemory()) oomUnsafe.crash("ExceptionHandlerBailout"); } if (retval == BAILOUT_RETURN_OK) { MOZ_ASSERT(bailoutInfo); // Overwrite the kind so HandleException after the bailout returns // false, jumping directly to the exception tail. if (excInfo.propagatingIonExceptionForDebugMode()) bailoutInfo->bailoutKind = Bailout_IonExceptionDebugMode; rfe->kind = ResumeFromException::RESUME_BAILOUT; rfe->target = cx->runtime()->jitRuntime()->getBailoutTail()->raw(); rfe->bailoutInfo = bailoutInfo; } else { // Bailout failed. If the overrecursion check failed, clear the // exception to turn this into an uncatchable error, continue popping // all inline frames and have the caller report the error. MOZ_ASSERT(!bailoutInfo); if (retval == BAILOUT_RETURN_OVERRECURSED) { *overrecursed = true; if (!excInfo.propagatingIonExceptionForDebugMode()) cx->clearPendingException(); } else { MOZ_ASSERT(retval == BAILOUT_RETURN_FATAL_ERROR); // Crash for now so as not to complicate the exception handling code // further. MOZ_CRASH(); } } // Make the frame being bailed out the top profiled frame. if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) cx->runtime()->jitActivation->setLastProfilingFrame(currentFramePtr); return retval; } // Initialize the decl env Object, call object, and any arguments obj of the // current frame. bool jit::EnsureHasEnvironmentObjects(JSContext* cx, AbstractFramePtr fp) { // Ion does not compile eval scripts. MOZ_ASSERT(!fp.isEvalFrame()); if (fp.isFunctionFrame()) { // Ion does not handle extra var environments due to parameter // expressions yet. MOZ_ASSERT(!fp.callee()->needsExtraBodyVarEnvironment()); if (!fp.hasInitialEnvironment() && fp.callee()->needsFunctionEnvironmentObjects()) { if (!fp.initFunctionEnvironmentObjects(cx)) return false; } } return true; } void jit::CheckFrequentBailouts(JSContext* cx, JSScript* script, BailoutKind bailoutKind) { if (script->hasIonScript()) { // Invalidate if this script keeps bailing out without invalidation. Next time // we compile this script LICM will be disabled. IonScript* ionScript = script->ionScript(); if (ionScript->bailoutExpected()) { // If we bailout because of the first execution of a basic block, // then we should record which basic block we are returning in, // which should prevent this from happening again. Also note that // the first execution bailout can be related to an inlined script, // so there is no need to penalize the caller. if (bailoutKind != Bailout_FirstExecution && !script->hadFrequentBailouts()) script->setHadFrequentBailouts(); JitSpew(JitSpew_IonInvalidate, "Invalidating due to too many bailouts"); Invalidate(cx, script); } } } void BailoutFrameInfo::attachOnJitActivation(const JitActivationIterator& jitActivations) { MOZ_ASSERT(jitActivations.jitTop() == FAKE_JIT_TOP_FOR_BAILOUT); activation_ = jitActivations->asJit(); activation_->setBailoutData(this); } BailoutFrameInfo::~BailoutFrameInfo() { activation_->cleanBailoutData(); }