315 lines
12 KiB
C++
315 lines
12 KiB
C++
/* -*- 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();
|
|
}
|