summaryrefslogtreecommitdiffstats
path: root/js/src/jit/Bailouts.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/Bailouts.cpp')
-rw-r--r--js/src/jit/Bailouts.cpp314
1 files changed, 314 insertions, 0 deletions
diff --git a/js/src/jit/Bailouts.cpp b/js/src/jit/Bailouts.cpp
new file mode 100644
index 000000000..d5172c6a3
--- /dev/null
+++ b/js/src/jit/Bailouts.cpp
@@ -0,0 +1,314 @@
+/* -*- 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();
+}