blob: cf8f92446a2a8f69ea79c8d91cefcc1e23aa703a [file] [log] [blame]
/* -*- 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 "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 = GetJSContextFromJitCode();
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);
EnsureExitFrame(iter.jsFrame());
}
// 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 = GetJSContextFromJitCode();
// 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);
JitFrameLayout* frame = iter.jsFrame();
JitSpew(JitSpew_IonInvalidate, "Bailout failed (%s): converting to exit frame",
(retval == BAILOUT_RETURN_FATAL_ERROR) ? "Fatal Error" : "Over Recursion");
JitSpew(JitSpew_IonInvalidate, " orig calleeToken %p", (void*) frame->calleeToken());
JitSpew(JitSpew_IonInvalidate, " orig frameSize %u", unsigned(frame->prevFrameLocalSize()));
JitSpew(JitSpew_IonInvalidate, " orig ra %p", (void*) frame->returnAddress());
frame->replaceCalleeToken(nullptr);
EnsureExitFrame(frame);
JitSpew(JitSpew_IonInvalidate, " new calleeToken %p", (void*) frame->calleeToken());
JitSpew(JitSpew_IonInvalidate, " new frameSize %u", unsigned(frame->prevFrameLocalSize()));
JitSpew(JitSpew_IonInvalidate, " new ra %p", (void*) frame->returnAddress());
}
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());
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::EnsureHasScopeObjects(JSContext* cx, AbstractFramePtr fp)
{
if (fp.isFunctionFrame() &&
fp.fun()->needsCallObject() &&
!fp.hasCallObj())
{
return fp.initFunctionScopeObjects(cx);
}
return true;
}
bool
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->numBailouts() >= JitOptions.frequentBailoutThreshold) {
// 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");
if (!Invalidate(cx, script))
return false;
}
}
return true;
}
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();
}