summaryrefslogtreecommitdiffstats
path: root/js/src/jit/BaselineBailouts.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /js/src/jit/BaselineBailouts.cpp
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit/BaselineBailouts.cpp')
-rw-r--r--js/src/jit/BaselineBailouts.cpp2058
1 files changed, 2058 insertions, 0 deletions
diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp
new file mode 100644
index 0000000000..795476c4ac
--- /dev/null
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -0,0 +1,2058 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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 "mozilla/Assertions.h"
+#include "mozilla/ScopeExit.h"
+
+#include "builtin/ModuleObject.h"
+#include "debugger/DebugAPI.h"
+#include "gc/GC.h"
+#include "jit/arm/Simulator-arm.h"
+#include "jit/Bailouts.h"
+#include "jit/BaselineFrame.h"
+#include "jit/BaselineIC.h"
+#include "jit/BaselineJIT.h"
+#include "jit/CalleeToken.h"
+#include "jit/Invalidation.h"
+#include "jit/Ion.h"
+#include "jit/IonScript.h"
+#include "jit/JitFrames.h"
+#include "jit/JitRuntime.h"
+#include "jit/JitSpewer.h"
+#include "jit/loong64/Simulator-loong64.h"
+#include "jit/mips32/Simulator-mips32.h"
+#include "jit/mips64/Simulator-mips64.h"
+#include "jit/RematerializedFrame.h"
+#include "jit/SharedICRegisters.h"
+#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit, js::ReportOverRecursed
+#include "js/Utility.h"
+#include "util/Memory.h"
+#include "vm/ArgumentsObject.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/JitActivation.h"
+
+#include "jit/JitFrames-inl.h"
+#include "vm/JSContext-inl.h"
+#include "vm/JSScript-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+
+// BaselineStackBuilder may reallocate its buffer if the current one is too
+// small. To avoid dangling pointers, BufferPointer represents a pointer into
+// this buffer as a pointer to the header and a fixed offset.
+template <typename T>
+class BufferPointer {
+ const UniquePtr<BaselineBailoutInfo>& header_;
+ size_t offset_;
+ bool heap_;
+
+ public:
+ BufferPointer(const UniquePtr<BaselineBailoutInfo>& header, size_t offset,
+ bool heap)
+ : header_(header), offset_(offset), heap_(heap) {}
+
+ T* get() const {
+ BaselineBailoutInfo* header = header_.get();
+ if (!heap_) {
+ return (T*)(header->incomingStack + offset_);
+ }
+
+ uint8_t* p = header->copyStackTop - offset_;
+ MOZ_ASSERT(p >= header->copyStackBottom && p < header->copyStackTop);
+ return (T*)p;
+ }
+
+ void set(const T& value) { *get() = value; }
+
+ // Note: we return a copy instead of a reference, to avoid potential memory
+ // safety hazards when the underlying buffer gets resized.
+ const T operator*() const { return *get(); }
+ T* operator->() const { return get(); }
+};
+
+/**
+ * BaselineStackBuilder helps abstract the process of rebuilding the C stack on
+ * the heap. It takes a bailout iterator and keeps track of the point on the C
+ * stack from which the reconstructed frames will be written.
+ *
+ * It exposes methods to write data into the heap memory storing the
+ * reconstructed stack. It also exposes method to easily calculate addresses.
+ * This includes both the virtual address that a particular value will be at
+ * when it's eventually copied onto the stack, as well as the current actual
+ * address of that value (whether on the heap allocated portion being
+ * constructed or the existing stack).
+ *
+ * The abstraction handles transparent re-allocation of the heap memory when it
+ * needs to be enlarged to accommodate new data. Similarly to the C stack, the
+ * data that's written to the reconstructed stack grows from high to low in
+ * memory.
+ *
+ * The lowest region of the allocated memory contains a BaselineBailoutInfo
+ * structure that points to the start and end of the written data.
+ */
+class MOZ_STACK_CLASS BaselineStackBuilder {
+ JSContext* cx_;
+ JitFrameLayout* frame_ = nullptr;
+ SnapshotIterator& iter_;
+ RootedValueVector outermostFrameFormals_;
+
+ size_t bufferTotal_ = 1024;
+ size_t bufferAvail_ = 0;
+ size_t bufferUsed_ = 0;
+ size_t framePushed_ = 0;
+
+ UniquePtr<BaselineBailoutInfo> header_;
+
+ JSScript* script_;
+ JSFunction* fun_;
+ const ExceptionBailoutInfo* excInfo_;
+ ICScript* icScript_;
+
+ jsbytecode* pc_ = nullptr;
+ JSOp op_ = JSOp::Nop;
+ mozilla::Maybe<ResumeMode> resumeMode_;
+ uint32_t exprStackSlots_ = 0;
+ void* prevFramePtr_ = nullptr;
+ Maybe<BufferPointer<BaselineFrame>> blFrame_;
+
+ size_t frameNo_ = 0;
+ JSFunction* nextCallee_ = nullptr;
+
+ BailoutKind bailoutKind_;
+
+ // The baseline frames we will reconstruct on the heap are not
+ // rooted, so GC must be suppressed.
+ gc::AutoSuppressGC suppress_;
+
+ public:
+ BaselineStackBuilder(JSContext* cx, const JSJitFrameIter& frameIter,
+ SnapshotIterator& iter,
+ const ExceptionBailoutInfo* excInfo,
+ BailoutReason reason);
+
+ [[nodiscard]] bool init() {
+ MOZ_ASSERT(!header_);
+ MOZ_ASSERT(bufferUsed_ == 0);
+
+ uint8_t* bufferRaw = cx_->pod_calloc<uint8_t>(bufferTotal_);
+ if (!bufferRaw) {
+ return false;
+ }
+ bufferAvail_ = bufferTotal_ - sizeof(BaselineBailoutInfo);
+
+ header_.reset(new (bufferRaw) BaselineBailoutInfo());
+ header_->incomingStack = reinterpret_cast<uint8_t*>(frame_);
+ header_->copyStackTop = bufferRaw + bufferTotal_;
+ header_->copyStackBottom = header_->copyStackTop;
+ return true;
+ }
+
+ [[nodiscard]] bool buildOneFrame();
+ bool done();
+ void nextFrame();
+
+ JSScript* script() const { return script_; }
+ size_t frameNo() const { return frameNo_; }
+ bool isOutermostFrame() const { return frameNo_ == 0; }
+ MutableHandleValueVector outermostFrameFormals() {
+ return &outermostFrameFormals_;
+ }
+ BailoutKind bailoutKind() const { return bailoutKind_; }
+
+ inline JitFrameLayout* startFrame() { return frame_; }
+
+ BaselineBailoutInfo* info() {
+ MOZ_ASSERT(header_);
+ return header_.get();
+ }
+
+ BaselineBailoutInfo* takeBuffer() {
+ MOZ_ASSERT(header_);
+ return header_.release();
+ }
+
+ private:
+ [[nodiscard]] bool initFrame();
+ [[nodiscard]] bool buildBaselineFrame();
+ [[nodiscard]] bool buildArguments();
+ [[nodiscard]] bool buildFixedSlots();
+ [[nodiscard]] bool fixUpCallerArgs(MutableHandleValueVector savedCallerArgs,
+ bool* fixedUp);
+ [[nodiscard]] bool buildFinallyException();
+ [[nodiscard]] bool buildExpressionStack();
+ [[nodiscard]] bool finishLastFrame();
+
+ [[nodiscard]] bool prepareForNextFrame(HandleValueVector savedCallerArgs);
+ [[nodiscard]] bool finishOuterFrame();
+ [[nodiscard]] bool buildStubFrame(uint32_t frameSize,
+ HandleValueVector savedCallerArgs);
+ [[nodiscard]] bool buildRectifierFrame(uint32_t actualArgc,
+ size_t endOfBaselineStubArgs);
+
+#ifdef DEBUG
+ [[nodiscard]] bool validateFrame();
+#endif
+
+#ifdef DEBUG
+ bool envChainSlotCanBeOptimized();
+#endif
+
+ bool isPrologueBailout();
+ jsbytecode* getResumePC();
+ void* getStubReturnAddress();
+
+ uint32_t exprStackSlots() const { return exprStackSlots_; }
+
+ // Returns true if we're bailing out to a catch or finally block in this frame
+ bool catchingException() const {
+ return excInfo_ && excInfo_->catchingException() &&
+ excInfo_->frameNo() == frameNo_;
+ }
+
+ // Returns true if we're bailing out to a finally block in this frame.
+ bool resumingInFinallyBlock() const {
+ return catchingException() && excInfo_->isFinally();
+ }
+
+ bool forcedReturn() const { return excInfo_ && excInfo_->forcedReturn(); }
+
+ // Returns true if we're bailing out in place for debug mode
+ bool propagatingIonExceptionForDebugMode() const {
+ return excInfo_ && excInfo_->propagatingIonExceptionForDebugMode();
+ }
+
+ void* prevFramePtr() const {
+ MOZ_ASSERT(prevFramePtr_);
+ return prevFramePtr_;
+ }
+ BufferPointer<BaselineFrame>& blFrame() { return blFrame_.ref(); }
+
+ void setNextCallee(JSFunction* nextCallee);
+ JSFunction* nextCallee() const { return nextCallee_; }
+
+ jsbytecode* pc() const { return pc_; }
+ bool resumeAfter() const {
+ return !catchingException() && iter_.resumeAfter();
+ }
+
+ ResumeMode resumeMode() const { return *resumeMode_; }
+
+ bool needToSaveCallerArgs() const {
+ return resumeMode() == ResumeMode::InlinedAccessor;
+ }
+
+ [[nodiscard]] bool enlarge() {
+ MOZ_ASSERT(header_ != nullptr);
+ if (bufferTotal_ & mozilla::tl::MulOverflowMask<2>::value) {
+ ReportOutOfMemory(cx_);
+ return false;
+ }
+
+ size_t newSize = bufferTotal_ * 2;
+ uint8_t* newBufferRaw = cx_->pod_calloc<uint8_t>(newSize);
+ if (!newBufferRaw) {
+ return false;
+ }
+
+ // Initialize the new buffer.
+ //
+ // Before:
+ //
+ // [ Header | .. | Payload ]
+ //
+ // After:
+ //
+ // [ Header | ............... | Payload ]
+ //
+ // Size of Payload is |bufferUsed_|.
+ //
+ // We need to copy from the old buffer and header to the new buffer before
+ // we set header_ (this deletes the old buffer).
+ //
+ // We also need to update |copyStackBottom| and |copyStackTop| because these
+ // fields point to the Payload's start and end, respectively.
+ using BailoutInfoPtr = UniquePtr<BaselineBailoutInfo>;
+ BailoutInfoPtr newHeader(new (newBufferRaw) BaselineBailoutInfo(*header_));
+ newHeader->copyStackTop = newBufferRaw + newSize;
+ newHeader->copyStackBottom = newHeader->copyStackTop - bufferUsed_;
+ memcpy(newHeader->copyStackBottom, header_->copyStackBottom, bufferUsed_);
+ bufferTotal_ = newSize;
+ bufferAvail_ = newSize - (sizeof(BaselineBailoutInfo) + bufferUsed_);
+ header_ = std::move(newHeader);
+ return true;
+ }
+
+ void resetFramePushed() { framePushed_ = 0; }
+
+ size_t framePushed() const { return framePushed_; }
+
+ [[nodiscard]] bool subtract(size_t size, const char* info = nullptr) {
+ // enlarge the buffer if need be.
+ while (size > bufferAvail_) {
+ if (!enlarge()) {
+ return false;
+ }
+ }
+
+ // write out element.
+ header_->copyStackBottom -= size;
+ bufferAvail_ -= size;
+ bufferUsed_ += size;
+ framePushed_ += size;
+ if (info) {
+ JitSpew(JitSpew_BaselineBailouts, " SUB_%03d %p/%p %-15s",
+ (int)size, header_->copyStackBottom,
+ virtualPointerAtStackOffset(0), info);
+ }
+ return true;
+ }
+
+ template <typename T>
+ [[nodiscard]] bool write(const T& t) {
+ MOZ_ASSERT(!(uintptr_t(&t) >= uintptr_t(header_->copyStackBottom) &&
+ uintptr_t(&t) < uintptr_t(header_->copyStackTop)),
+ "Should not reference memory that can be freed");
+ if (!subtract(sizeof(T))) {
+ return false;
+ }
+ memcpy(header_->copyStackBottom, &t, sizeof(T));
+ return true;
+ }
+
+ template <typename T>
+ [[nodiscard]] bool writePtr(T* t, const char* info) {
+ if (!write<T*>(t)) {
+ return false;
+ }
+ if (info) {
+ JitSpew(JitSpew_BaselineBailouts, " WRITE_PTR %p/%p %-15s %p",
+ header_->copyStackBottom, virtualPointerAtStackOffset(0), info,
+ t);
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool writeWord(size_t w, const char* info) {
+ if (!write<size_t>(w)) {
+ return false;
+ }
+ if (info) {
+ if (sizeof(size_t) == 4) {
+ JitSpew(JitSpew_BaselineBailouts, " WRITE_WRD %p/%p %-15s %08zx",
+ header_->copyStackBottom, virtualPointerAtStackOffset(0), info,
+ w);
+ } else {
+ JitSpew(JitSpew_BaselineBailouts, " WRITE_WRD %p/%p %-15s %016zx",
+ header_->copyStackBottom, virtualPointerAtStackOffset(0), info,
+ w);
+ }
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool writeValue(const Value& val, const char* info) {
+ if (!write<Value>(val)) {
+ return false;
+ }
+ if (info) {
+ JitSpew(JitSpew_BaselineBailouts,
+ " WRITE_VAL %p/%p %-15s %016" PRIx64,
+ header_->copyStackBottom, virtualPointerAtStackOffset(0), info,
+ *((uint64_t*)&val));
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool maybeWritePadding(size_t alignment, size_t after,
+ const char* info) {
+ MOZ_ASSERT(framePushed_ % sizeof(Value) == 0);
+ MOZ_ASSERT(after % sizeof(Value) == 0);
+ size_t offset = ComputeByteAlignment(after, alignment);
+ while (framePushed_ % alignment != offset) {
+ if (!writeValue(MagicValue(JS_ARG_POISON), info)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void setResumeFramePtr(void* resumeFramePtr) {
+ header_->resumeFramePtr = resumeFramePtr;
+ }
+
+ void setResumeAddr(void* resumeAddr) { header_->resumeAddr = resumeAddr; }
+
+ template <typename T>
+ BufferPointer<T> pointerAtStackOffset(size_t offset) {
+ if (offset < bufferUsed_) {
+ // Calculate offset from copyStackTop.
+ offset = header_->copyStackTop - (header_->copyStackBottom + offset);
+ return BufferPointer<T>(header_, offset, /* heap = */ true);
+ }
+
+ return BufferPointer<T>(header_, offset - bufferUsed_, /* heap = */ false);
+ }
+
+ BufferPointer<Value> valuePointerAtStackOffset(size_t offset) {
+ return pointerAtStackOffset<Value>(offset);
+ }
+
+ inline uint8_t* virtualPointerAtStackOffset(size_t offset) {
+ if (offset < bufferUsed_) {
+ return reinterpret_cast<uint8_t*>(frame_) - (bufferUsed_ - offset);
+ }
+ return reinterpret_cast<uint8_t*>(frame_) + (offset - bufferUsed_);
+ }
+};
+
+BaselineStackBuilder::BaselineStackBuilder(JSContext* cx,
+ const JSJitFrameIter& frameIter,
+ SnapshotIterator& iter,
+ const ExceptionBailoutInfo* excInfo,
+ BailoutReason reason)
+ : cx_(cx),
+ frame_(static_cast<JitFrameLayout*>(frameIter.current())),
+ iter_(iter),
+ outermostFrameFormals_(cx),
+ script_(frameIter.script()),
+ fun_(frameIter.maybeCallee()),
+ excInfo_(excInfo),
+ icScript_(script_->jitScript()->icScript()),
+ bailoutKind_(iter.bailoutKind()),
+ suppress_(cx) {
+ MOZ_ASSERT(bufferTotal_ >= sizeof(BaselineBailoutInfo));
+ if (reason == BailoutReason::Invalidate) {
+ bailoutKind_ = BailoutKind::OnStackInvalidation;
+ }
+}
+
+bool BaselineStackBuilder::initFrame() {
+ // Get the pc and ResumeMode. If we are handling an exception, resume at the
+ // pc of the catch or finally block.
+ if (catchingException()) {
+ pc_ = excInfo_->resumePC();
+ resumeMode_ = mozilla::Some(ResumeMode::ResumeAt);
+ } else {
+ pc_ = script_->offsetToPC(iter_.pcOffset());
+ resumeMode_ = mozilla::Some(iter_.resumeMode());
+ }
+ op_ = JSOp(*pc_);
+
+ // If we are catching an exception, we are bailing out to a catch or
+ // finally block and this is the frame where we will resume. Usually the
+ // expression stack should be empty in this case but there can be
+ // iterators on the stack.
+ if (catchingException()) {
+ exprStackSlots_ = excInfo_->numExprSlots();
+ } else {
+ uint32_t totalFrameSlots = iter_.numAllocations();
+ uint32_t fixedSlots = script_->nfixed();
+ uint32_t argSlots = CountArgSlots(script_, fun_);
+ uint32_t intermediates = NumIntermediateValues(resumeMode());
+ exprStackSlots_ = totalFrameSlots - fixedSlots - argSlots - intermediates;
+
+ // Verify that there was no underflow.
+ MOZ_ASSERT(exprStackSlots_ <= totalFrameSlots);
+ }
+
+ JitSpew(JitSpew_BaselineBailouts, " Unpacking %s:%u:%u",
+ script_->filename(), script_->lineno(), script_->column());
+ JitSpew(JitSpew_BaselineBailouts, " [BASELINE-JS FRAME]");
+
+ // Write the previous frame pointer value. For the outermost frame we reuse
+ // the value in the JitFrameLayout already on the stack. Record the virtual
+ // stack offset at this location. Later on, if we end up writing out a
+ // BaselineStub frame for the next callee, we'll need to save the address.
+ if (!isOutermostFrame()) {
+ if (!writePtr(prevFramePtr(), "PrevFramePtr")) {
+ return false;
+ }
+ }
+ prevFramePtr_ = virtualPointerAtStackOffset(0);
+
+ resetFramePushed();
+
+ return true;
+}
+
+void BaselineStackBuilder::setNextCallee(JSFunction* nextCallee) {
+ nextCallee_ = nextCallee;
+
+ // Update icScript_ to point to the icScript of nextCallee
+ const uint32_t pcOff = script_->pcToOffset(pc_);
+ icScript_ = icScript_->findInlinedChild(pcOff);
+}
+
+bool BaselineStackBuilder::done() {
+ if (!iter_.moreFrames()) {
+ MOZ_ASSERT(!nextCallee_);
+ return true;
+ }
+ return catchingException();
+}
+
+void BaselineStackBuilder::nextFrame() {
+ MOZ_ASSERT(nextCallee_);
+ fun_ = nextCallee_;
+ script_ = fun_->nonLazyScript();
+ nextCallee_ = nullptr;
+
+ // Scripts with an IonScript must also have a BaselineScript.
+ MOZ_ASSERT(script_->hasBaselineScript());
+
+ frameNo_++;
+ iter_.nextInstruction();
+}
+
+// Build the BaselineFrame struct
+bool BaselineStackBuilder::buildBaselineFrame() {
+ if (!subtract(BaselineFrame::Size(), "BaselineFrame")) {
+ return false;
+ }
+ blFrame_.reset();
+ blFrame_.emplace(pointerAtStackOffset<BaselineFrame>(0));
+
+ uint32_t flags = BaselineFrame::RUNNING_IN_INTERPRETER;
+
+ // If we are bailing to a script whose execution is observed, mark the
+ // baseline frame as a debuggee frame. This is to cover the case where we
+ // don't rematerialize the Ion frame via the Debugger.
+ if (script_->isDebuggee()) {
+ flags |= BaselineFrame::DEBUGGEE;
+ }
+
+ // Get |envChain|.
+ JSObject* envChain = nullptr;
+ Value envChainSlot = iter_.read();
+ if (envChainSlot.isObject()) {
+ // The env slot has been updated from UndefinedValue. It must be the
+ // complete initial environment.
+ envChain = &envChainSlot.toObject();
+
+ // Set the HAS_INITIAL_ENV flag if needed. See IsFrameInitialEnvironment.
+ MOZ_ASSERT(!script_->isForEval());
+ if (fun_ && fun_->needsFunctionEnvironmentObjects()) {
+ MOZ_ASSERT(fun_->nonLazyScript()->initialEnvironmentShape());
+ flags |= BaselineFrame::HAS_INITIAL_ENV;
+ }
+ } else {
+ MOZ_ASSERT(envChainSlot.isUndefined() ||
+ envChainSlot.isMagic(JS_OPTIMIZED_OUT));
+ MOZ_ASSERT(envChainSlotCanBeOptimized());
+
+ // The env slot has been optimized out.
+ // Get it from the function or script.
+ if (fun_) {
+ envChain = fun_->environment();
+ } else if (script_->isModule()) {
+ envChain = script_->module()->environment();
+ } else {
+ // For global scripts without a non-syntactic env the env
+ // chain is the script's global lexical environment. (We do
+ // not compile scripts with a non-syntactic global scope).
+ // Also note that it's invalid to resume into the prologue in
+ // this case because the prologue expects the env chain in R1
+ // for eval and global scripts.
+ MOZ_ASSERT(!script_->isForEval());
+ MOZ_ASSERT(!script_->hasNonSyntacticScope());
+ envChain = &(script_->global().lexicalEnvironment());
+ }
+ }
+
+ // Write |envChain|.
+ MOZ_ASSERT(envChain);
+ JitSpew(JitSpew_BaselineBailouts, " EnvChain=%p", envChain);
+ blFrame()->setEnvironmentChain(envChain);
+
+ // Get |returnValue| if present.
+ Value returnValue = UndefinedValue();
+ if (script_->noScriptRval()) {
+ // Don't use the return value (likely a JS_OPTIMIZED_OUT MagicValue) to
+ // not confuse Baseline.
+ iter_.skip();
+ } else {
+ returnValue = iter_.read();
+ flags |= BaselineFrame::HAS_RVAL;
+ }
+
+ // Write |returnValue|.
+ JitSpew(JitSpew_BaselineBailouts, " ReturnValue=%016" PRIx64,
+ *((uint64_t*)&returnValue));
+ blFrame()->setReturnValue(returnValue);
+
+ // Get |argsObj| if present.
+ ArgumentsObject* argsObj = nullptr;
+ if (script_->needsArgsObj()) {
+ Value maybeArgsObj = iter_.read();
+ MOZ_ASSERT(maybeArgsObj.isObject() || maybeArgsObj.isUndefined() ||
+ maybeArgsObj.isMagic(JS_OPTIMIZED_OUT));
+ if (maybeArgsObj.isObject()) {
+ argsObj = &maybeArgsObj.toObject().as<ArgumentsObject>();
+ }
+ }
+
+ // Note: we do not need to initialize the scratchValue field in BaselineFrame.
+
+ // Write |flags|.
+ blFrame()->setFlags(flags);
+
+ // Write |icScript|.
+ JitSpew(JitSpew_BaselineBailouts, " ICScript=%p", icScript_);
+ blFrame()->setICScript(icScript_);
+
+ // initArgsObjUnchecked modifies the frame's flags, so call it after setFlags.
+ if (argsObj) {
+ blFrame()->initArgsObjUnchecked(*argsObj);
+ }
+ return true;
+}
+
+// Overwrite the pushed args present in the calling frame with
+// the unpacked |thisv| and argument values.
+bool BaselineStackBuilder::buildArguments() {
+ Value thisv = iter_.read();
+ JitSpew(JitSpew_BaselineBailouts, " Is function!");
+ JitSpew(JitSpew_BaselineBailouts, " thisv=%016" PRIx64,
+ *((uint64_t*)&thisv));
+
+ size_t thisvOffset = framePushed() + JitFrameLayout::offsetOfThis();
+ valuePointerAtStackOffset(thisvOffset).set(thisv);
+
+ MOZ_ASSERT(iter_.numAllocations() >= CountArgSlots(script_, fun_));
+ JitSpew(JitSpew_BaselineBailouts,
+ " frame slots %u, nargs %zu, nfixed %zu", iter_.numAllocations(),
+ fun_->nargs(), script_->nfixed());
+
+ bool shouldStoreOutermostFormals =
+ isOutermostFrame() && !script_->argsObjAliasesFormals();
+ if (shouldStoreOutermostFormals) {
+ // This is the first (outermost) frame and we don't have an
+ // arguments object aliasing the formals. Due to UCE and phi
+ // elimination, we could store an UndefinedValue() here for
+ // formals we think are unused, but locals may still reference the
+ // original argument slot (MParameter/LArgument) and expect the
+ // original Value. To avoid this problem, store the formals in a
+ // Vector until we are done.
+ MOZ_ASSERT(outermostFrameFormals().empty());
+ if (!outermostFrameFormals().resize(fun_->nargs())) {
+ return false;
+ }
+ }
+
+ for (uint32_t i = 0; i < fun_->nargs(); i++) {
+ Value arg = iter_.read();
+ JitSpew(JitSpew_BaselineBailouts, " arg %d = %016" PRIx64, (int)i,
+ *((uint64_t*)&arg));
+ if (!isOutermostFrame()) {
+ size_t argOffset = framePushed() + JitFrameLayout::offsetOfActualArg(i);
+ valuePointerAtStackOffset(argOffset).set(arg);
+ } else if (shouldStoreOutermostFormals) {
+ outermostFrameFormals()[i].set(arg);
+ } else {
+ // When the arguments object aliases the formal arguments, then
+ // JSOp::SetArg mutates the argument object. In such cases, the
+ // list of arguments reported by the snapshot are only aliases
+ // of argument object slots which are optimized to only store
+ // differences compared to arguments which are on the stack.
+ }
+ }
+ return true;
+}
+
+bool BaselineStackBuilder::buildFixedSlots() {
+ for (uint32_t i = 0; i < script_->nfixed(); i++) {
+ Value slot = iter_.read();
+ if (!writeValue(slot, "FixedValue")) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// The caller side of inlined js::fun_call and accessors must look
+// like the function wasn't inlined.
+bool BaselineStackBuilder::fixUpCallerArgs(
+ MutableHandleValueVector savedCallerArgs, bool* fixedUp) {
+ MOZ_ASSERT(!*fixedUp);
+
+ // Inlining of SpreadCall-like frames not currently supported.
+ MOZ_ASSERT(!IsSpreadOp(op_));
+
+ if (resumeMode() != ResumeMode::InlinedFunCall && !needToSaveCallerArgs()) {
+ return true;
+ }
+
+ // Calculate how many arguments are consumed by the inlined call.
+ // All calls pass |callee| and |this|.
+ uint32_t inlinedArgs = 2;
+ if (resumeMode() == ResumeMode::InlinedFunCall) {
+ // The first argument to an inlined FunCall becomes |this|,
+ // if it exists. The rest are passed normally.
+ MOZ_ASSERT(IsInvokeOp(op_));
+ inlinedArgs += GET_ARGC(pc_) > 0 ? GET_ARGC(pc_) - 1 : 0;
+ } else {
+ MOZ_ASSERT(resumeMode() == ResumeMode::InlinedAccessor);
+ MOZ_ASSERT(IsIonInlinableGetterOrSetterOp(op_));
+ // Setters are passed one argument. Getters are passed none.
+ if (IsSetPropOp(op_)) {
+ inlinedArgs++;
+ }
+ }
+
+ // Calculate how many values are live on the stack across the call,
+ // and push them.
+ MOZ_ASSERT(inlinedArgs <= exprStackSlots());
+ uint32_t liveStackSlots = exprStackSlots() - inlinedArgs;
+
+ JitSpew(JitSpew_BaselineBailouts,
+ " pushing %u expression stack slots before fixup",
+ liveStackSlots);
+ for (uint32_t i = 0; i < liveStackSlots; i++) {
+ Value v = iter_.read();
+ if (!writeValue(v, "StackValue")) {
+ return false;
+ }
+ }
+
+ // When we inline js::fun_call, we bypass the native and inline the
+ // target directly. When rebuilding the stack, we need to fill in
+ // the right number of slots to make it look like the js_native was
+ // actually called.
+ if (resumeMode() == ResumeMode::InlinedFunCall) {
+ // We must transform the stack from |target, this, args| to
+ // |js_fun_call, target, this, args|. The value of |js_fun_call|
+ // will never be observed, so we push |undefined| for it, followed
+ // by the remaining arguments.
+ JitSpew(JitSpew_BaselineBailouts,
+ " pushing undefined to fixup funcall");
+ if (!writeValue(UndefinedValue(), "StackValue")) {
+ return false;
+ }
+ if (GET_ARGC(pc_) > 0) {
+ JitSpew(JitSpew_BaselineBailouts,
+ " pushing %u expression stack slots", inlinedArgs);
+ for (uint32_t i = 0; i < inlinedArgs; i++) {
+ Value arg = iter_.read();
+ if (!writeValue(arg, "StackValue")) {
+ return false;
+ }
+ }
+ } else {
+ // When we inline FunCall with no arguments, we push an extra
+ // |undefined| value for |this|. That value should not appear
+ // in the rebuilt baseline frame.
+ JitSpew(JitSpew_BaselineBailouts, " pushing target of funcall");
+ Value target = iter_.read();
+ if (!writeValue(target, "StackValue")) {
+ return false;
+ }
+ // Skip |this|.
+ iter_.skip();
+ }
+ }
+
+ if (needToSaveCallerArgs()) {
+ // Save the actual arguments. They are needed to rebuild the callee frame.
+ if (!savedCallerArgs.resize(inlinedArgs)) {
+ return false;
+ }
+ for (uint32_t i = 0; i < inlinedArgs; i++) {
+ savedCallerArgs[i].set(iter_.read());
+ }
+
+ if (IsSetPropOp(op_)) {
+ // The RHS argument to SetProp remains on the stack after the
+ // operation and is observable, so we have to fill it in.
+ Value initialArg = savedCallerArgs[inlinedArgs - 1];
+ JitSpew(JitSpew_BaselineBailouts,
+ " pushing setter's initial argument");
+ if (!writeValue(initialArg, "StackValue")) {
+ return false;
+ }
+ }
+ }
+
+ *fixedUp = true;
+ return true;
+}
+
+bool BaselineStackBuilder::buildExpressionStack() {
+ JitSpew(JitSpew_BaselineBailouts, " pushing %u expression stack slots",
+ exprStackSlots());
+ for (uint32_t i = 0; i < exprStackSlots(); i++) {
+ Value v;
+ // If we are in the middle of propagating an exception from Ion by
+ // bailing to baseline due to debug mode, we might not have all
+ // the stack if we are at the newest frame.
+ //
+ // For instance, if calling |f()| pushed an Ion frame which threw,
+ // the snapshot expects the return value to be pushed, but it's
+ // possible nothing was pushed before we threw.
+ //
+ // We therefore use a fallible read here.
+ if (!iter_.tryRead(&v)) {
+ MOZ_ASSERT(propagatingIonExceptionForDebugMode() && !iter_.moreFrames());
+ v = MagicValue(JS_OPTIMIZED_OUT);
+ }
+ if (!writeValue(v, "StackValue")) {
+ return false;
+ }
+ }
+
+ if (resumeMode() == ResumeMode::ResumeAfterCheckIsObject) {
+ JitSpew(JitSpew_BaselineBailouts,
+ " Checking that intermediate value is an object");
+ Value returnVal;
+ if (iter_.tryRead(&returnVal) && !returnVal.isObject()) {
+ MOZ_ASSERT(!returnVal.isMagic());
+ JitSpew(JitSpew_BaselineBailouts,
+ " Not an object! Overwriting bailout kind");
+ bailoutKind_ = BailoutKind::ThrowCheckIsObject;
+ }
+ }
+
+ return true;
+}
+
+bool BaselineStackBuilder::buildFinallyException() {
+ MOZ_ASSERT(resumingInFinallyBlock());
+
+ if (!writeValue(excInfo_->finallyException(), "Exception")) {
+ return false;
+ }
+ if (!writeValue(BooleanValue(true), "throwing")) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BaselineStackBuilder::prepareForNextFrame(
+ HandleValueVector savedCallerArgs) {
+ const uint32_t frameSize = framePushed();
+
+ // Write out descriptor and return address for the baseline frame.
+ // The icEntry in question MUST have an inlinable fallback stub.
+ if (!finishOuterFrame()) {
+ return false;
+ }
+
+ return buildStubFrame(frameSize, savedCallerArgs);
+}
+
+bool BaselineStackBuilder::finishOuterFrame() {
+ // . .
+ // | Descr(BLJS) |
+ // +---------------+
+ // | ReturnAddr |
+ // +===============+
+
+ const BaselineInterpreter& baselineInterp =
+ cx_->runtime()->jitRuntime()->baselineInterpreter();
+
+ blFrame()->setInterpreterFields(script_, pc_);
+
+ // Write out descriptor of BaselineJS frame.
+ size_t baselineFrameDescr = MakeFrameDescriptor(FrameType::BaselineJS);
+ if (!writeWord(baselineFrameDescr, "Descriptor")) {
+ return false;
+ }
+
+ uint8_t* retAddr = baselineInterp.retAddrForIC(op_);
+ return writePtr(retAddr, "ReturnAddr");
+}
+
+bool BaselineStackBuilder::buildStubFrame(uint32_t frameSize,
+ HandleValueVector savedCallerArgs) {
+ // Build baseline stub frame:
+ // +===============+
+ // | FramePtr |
+ // +---------------+
+ // | StubPtr |
+ // +---------------+
+ // | Padding? |
+ // +---------------+
+ // | ArgA |
+ // +---------------+
+ // | ... |
+ // +---------------+
+ // | Arg0 |
+ // +---------------+
+ // | ThisV |
+ // +---------------+
+ // | CalleeToken |
+ // +---------------+
+ // | Descr(BLStub) |
+ // +---------------+
+ // | ReturnAddr |
+ // +===============+
+
+ JitSpew(JitSpew_BaselineBailouts, " [BASELINE-STUB FRAME]");
+
+ // Write previous frame pointer (saved earlier).
+ if (!writePtr(prevFramePtr(), "PrevFramePtr")) {
+ return false;
+ }
+ prevFramePtr_ = virtualPointerAtStackOffset(0);
+
+ // Write stub pointer.
+ uint32_t pcOff = script_->pcToOffset(pc_);
+ JitScript* jitScript = script_->jitScript();
+ const ICEntry& icEntry = jitScript->icEntryFromPCOffset(pcOff);
+ ICFallbackStub* fallback = jitScript->fallbackStubForICEntry(&icEntry);
+ if (!writePtr(fallback, "StubPtr")) {
+ return false;
+ }
+
+ // Write out the arguments, copied from the baseline frame. The order
+ // of the arguments is reversed relative to the baseline frame's stack
+ // values.
+ MOZ_ASSERT(IsIonInlinableOp(op_));
+ bool pushedNewTarget = IsConstructPC(pc_);
+ unsigned actualArgc;
+ Value callee;
+ if (needToSaveCallerArgs()) {
+ // For accessors, the arguments are not on the stack anymore,
+ // but they are copied in a vector and are written here.
+ callee = savedCallerArgs[0];
+ actualArgc = IsSetPropOp(op_) ? 1 : 0;
+
+ // Align the stack based on the number of arguments.
+ size_t afterFrameSize =
+ (actualArgc + 1) * sizeof(Value) + JitFrameLayout::Size();
+ if (!maybeWritePadding(JitStackAlignment, afterFrameSize, "Padding")) {
+ return false;
+ }
+
+ // Push arguments.
+ MOZ_ASSERT(actualArgc + 2 <= exprStackSlots());
+ MOZ_ASSERT(savedCallerArgs.length() == actualArgc + 2);
+ for (unsigned i = 0; i < actualArgc + 1; i++) {
+ size_t arg = savedCallerArgs.length() - (i + 1);
+ if (!writeValue(savedCallerArgs[arg], "ArgVal")) {
+ return false;
+ }
+ }
+ } else if (resumeMode() == ResumeMode::InlinedFunCall && GET_ARGC(pc_) == 0) {
+ // When calling FunCall with 0 arguments, we push |undefined|
+ // for this. See BaselineCacheIRCompiler::pushFunCallArguments.
+ MOZ_ASSERT(!pushedNewTarget);
+ actualArgc = 0;
+ // Align the stack based on pushing |this| and 0 arguments.
+ size_t afterFrameSize = sizeof(Value) + JitFrameLayout::Size();
+ if (!maybeWritePadding(JitStackAlignment, afterFrameSize, "Padding")) {
+ return false;
+ }
+ // Push an undefined value for |this|.
+ if (!writeValue(UndefinedValue(), "ThisValue")) {
+ return false;
+ }
+ size_t calleeSlot = blFrame()->numValueSlots(frameSize) - 1;
+ callee = *blFrame()->valueSlot(calleeSlot);
+
+ } else {
+ MOZ_ASSERT(resumeMode() == ResumeMode::InlinedStandardCall ||
+ resumeMode() == ResumeMode::InlinedFunCall);
+ actualArgc = GET_ARGC(pc_);
+ if (resumeMode() == ResumeMode::InlinedFunCall) {
+ // See BaselineCacheIRCompiler::pushFunCallArguments.
+ MOZ_ASSERT(actualArgc > 0);
+ actualArgc--;
+ }
+
+ // In addition to the formal arguments, we must also push |this|.
+ // When calling a constructor, we must also push |newTarget|.
+ uint32_t numArguments = actualArgc + 1 + pushedNewTarget;
+
+ // Align the stack based on the number of arguments.
+ size_t afterFrameSize =
+ numArguments * sizeof(Value) + JitFrameLayout::Size();
+ if (!maybeWritePadding(JitStackAlignment, afterFrameSize, "Padding")) {
+ return false;
+ }
+
+ // Copy the arguments and |this| from the BaselineFrame, in reverse order.
+ size_t valueSlot = blFrame()->numValueSlots(frameSize) - 1;
+ size_t calleeSlot = valueSlot - numArguments;
+
+ for (size_t i = valueSlot; i > calleeSlot; i--) {
+ Value v = *blFrame()->valueSlot(i);
+ if (!writeValue(v, "ArgVal")) {
+ return false;
+ }
+ }
+
+ callee = *blFrame()->valueSlot(calleeSlot);
+ }
+
+ // In case these arguments need to be copied on the stack again for a
+ // rectifier frame, save the framePushed values here for later use.
+ size_t endOfBaselineStubArgs = framePushed();
+
+ // Push callee token (must be a JS Function)
+ JitSpew(JitSpew_BaselineBailouts, " Callee = %016" PRIx64,
+ callee.asRawBits());
+
+ JSFunction* calleeFun = &callee.toObject().as<JSFunction>();
+ if (!writePtr(CalleeToToken(calleeFun, pushedNewTarget), "CalleeToken")) {
+ return false;
+ }
+ setNextCallee(calleeFun);
+
+ // Push BaselineStub frame descriptor
+ size_t baselineStubFrameDescr =
+ MakeFrameDescriptorForJitCall(FrameType::BaselineStub, actualArgc);
+ if (!writeWord(baselineStubFrameDescr, "Descriptor")) {
+ return false;
+ }
+
+ // Push return address into ICCall_Scripted stub, immediately after the call.
+ void* baselineCallReturnAddr = getStubReturnAddress();
+ MOZ_ASSERT(baselineCallReturnAddr);
+ if (!writePtr(baselineCallReturnAddr, "ReturnAddr")) {
+ return false;
+ }
+
+ // The stack must be aligned after the callee pushes the frame pointer.
+ MOZ_ASSERT((framePushed() + sizeof(void*)) % JitStackAlignment == 0);
+
+ // Build a rectifier frame if necessary
+ if (actualArgc < calleeFun->nargs() &&
+ !buildRectifierFrame(actualArgc, endOfBaselineStubArgs)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BaselineStackBuilder::buildRectifierFrame(uint32_t actualArgc,
+ size_t endOfBaselineStubArgs) {
+ // Push a reconstructed rectifier frame.
+ // +===============+
+ // | Padding? |
+ // +---------------+
+ // | UndefinedU |
+ // +---------------+
+ // | ... |
+ // +---------------+
+ // | Undefined0 |
+ // +---------------+
+ // | ArgA |
+ // +---------------+
+ // | ... |
+ // +---------------+
+ // | Arg0 |
+ // +---------------+
+ // | ThisV |
+ // +---------------+
+ // | CalleeToken |
+ // +---------------+
+ // | Descr(Rect) |
+ // +---------------+
+ // | ReturnAddr |
+ // +===============+
+
+ JitSpew(JitSpew_BaselineBailouts, " [RECTIFIER FRAME]");
+ bool pushedNewTarget = IsConstructPC(pc_);
+
+ if (!writePtr(prevFramePtr(), "PrevFramePtr")) {
+ return false;
+ }
+ prevFramePtr_ = virtualPointerAtStackOffset(0);
+
+ // Align the stack based on the number of arguments.
+ size_t afterFrameSize =
+ (nextCallee()->nargs() + 1 + pushedNewTarget) * sizeof(Value) +
+ RectifierFrameLayout::Size();
+ if (!maybeWritePadding(JitStackAlignment, afterFrameSize, "Padding")) {
+ return false;
+ }
+
+ // Copy new.target, if necessary.
+ if (pushedNewTarget) {
+ size_t newTargetOffset = (framePushed() - endOfBaselineStubArgs) +
+ (actualArgc + 1) * sizeof(Value);
+ Value newTargetValue = *valuePointerAtStackOffset(newTargetOffset);
+ if (!writeValue(newTargetValue, "CopiedNewTarget")) {
+ return false;
+ }
+ }
+
+ // Push undefined for missing arguments.
+ for (unsigned i = 0; i < (nextCallee()->nargs() - actualArgc); i++) {
+ if (!writeValue(UndefinedValue(), "FillerVal")) {
+ return false;
+ }
+ }
+
+ // Copy arguments + thisv from BaselineStub frame.
+ if (!subtract((actualArgc + 1) * sizeof(Value), "CopiedArgs")) {
+ return false;
+ }
+ BufferPointer<uint8_t> stubArgsEnd =
+ pointerAtStackOffset<uint8_t>(framePushed() - endOfBaselineStubArgs);
+ JitSpew(JitSpew_BaselineBailouts, " MemCpy from %p", stubArgsEnd.get());
+ memcpy(pointerAtStackOffset<uint8_t>(0).get(), stubArgsEnd.get(),
+ (actualArgc + 1) * sizeof(Value));
+
+ // Push calleeToken again.
+ if (!writePtr(CalleeToToken(nextCallee(), pushedNewTarget), "CalleeToken")) {
+ return false;
+ }
+
+ // Push rectifier frame descriptor
+ size_t rectifierFrameDescr =
+ MakeFrameDescriptorForJitCall(FrameType::Rectifier, actualArgc);
+ if (!writeWord(rectifierFrameDescr, "Descriptor")) {
+ return false;
+ }
+
+ // Push return address into the ArgumentsRectifier code, immediately after the
+ // ioncode call.
+ void* rectReturnAddr =
+ cx_->runtime()->jitRuntime()->getArgumentsRectifierReturnAddr().value;
+ MOZ_ASSERT(rectReturnAddr);
+ if (!writePtr(rectReturnAddr, "ReturnAddr")) {
+ return false;
+ }
+
+ // The stack must be aligned after the callee pushes the frame pointer.
+ MOZ_ASSERT((framePushed() + sizeof(void*)) % JitStackAlignment == 0);
+
+ return true;
+}
+
+bool BaselineStackBuilder::finishLastFrame() {
+ const BaselineInterpreter& baselineInterp =
+ cx_->runtime()->jitRuntime()->baselineInterpreter();
+
+ setResumeFramePtr(prevFramePtr());
+
+ // Compute the native address (within the Baseline Interpreter) that we will
+ // resume at and initialize the frame's interpreter fields.
+ uint8_t* resumeAddr;
+ if (isPrologueBailout()) {
+ JitSpew(JitSpew_BaselineBailouts, " Resuming into prologue.");
+ MOZ_ASSERT(pc_ == script_->code());
+ blFrame()->setInterpreterFieldsForPrologue(script_);
+ resumeAddr = baselineInterp.bailoutPrologueEntryAddr();
+ } else if (propagatingIonExceptionForDebugMode()) {
+ // When propagating an exception for debug mode, set the
+ // resume pc to the throwing pc, so that Debugger hooks report
+ // the correct pc offset of the throwing op instead of its
+ // successor.
+ jsbytecode* throwPC = script_->offsetToPC(iter_.pcOffset());
+ blFrame()->setInterpreterFields(script_, throwPC);
+ resumeAddr = baselineInterp.interpretOpAddr().value;
+ } else {
+ jsbytecode* resumePC = getResumePC();
+ blFrame()->setInterpreterFields(script_, resumePC);
+ resumeAddr = baselineInterp.interpretOpAddr().value;
+ }
+ setResumeAddr(resumeAddr);
+ JitSpew(JitSpew_BaselineBailouts, " Set resumeAddr=%p", resumeAddr);
+
+ if (cx_->runtime()->geckoProfiler().enabled()) {
+ // Register bailout with profiler.
+ const char* filename = script_->filename();
+ if (filename == nullptr) {
+ filename = "<unknown>";
+ }
+ unsigned len = strlen(filename) + 200;
+ UniqueChars buf(js_pod_malloc<char>(len));
+ if (buf == nullptr) {
+ ReportOutOfMemory(cx_);
+ return false;
+ }
+ snprintf(buf.get(), len, "%s %s %s on line %u of %s:%u",
+ BailoutKindString(bailoutKind()), resumeAfter() ? "after" : "at",
+ CodeName(op_), PCToLineNumber(script_, pc_), filename,
+ script_->lineno());
+ cx_->runtime()->geckoProfiler().markEvent("Bailout", buf.get());
+ }
+
+ return true;
+}
+
+#ifdef DEBUG
+// The |envChain| slot must not be optimized out if the currently
+// active scope requires any EnvironmentObjects beyond what is
+// available at body scope. This checks that scope chain does not
+// require any such EnvironmentObjects.
+// See also: |CompileInfo::isObservableFrameSlot|
+bool BaselineStackBuilder::envChainSlotCanBeOptimized() {
+ jsbytecode* pc = script_->offsetToPC(iter_.pcOffset());
+ Scope* scopeIter = script_->innermostScope(pc);
+ while (scopeIter != script_->bodyScope()) {
+ if (!scopeIter || scopeIter->hasEnvironment()) {
+ return false;
+ }
+ scopeIter = scopeIter->enclosing();
+ }
+ return true;
+}
+
+bool jit::AssertBailoutStackDepth(JSContext* cx, JSScript* script,
+ jsbytecode* pc, ResumeMode mode,
+ uint32_t exprStackSlots) {
+ if (IsResumeAfter(mode)) {
+ pc = GetNextPc(pc);
+ }
+
+ uint32_t expectedDepth;
+ bool reachablePC;
+ if (!ReconstructStackDepth(cx, script, pc, &expectedDepth, &reachablePC)) {
+ return false;
+ }
+ if (!reachablePC) {
+ return true;
+ }
+
+ JSOp op = JSOp(*pc);
+
+ if (mode == ResumeMode::InlinedFunCall) {
+ // For inlined fun.call(this, ...); the reconstructed stack depth will
+ // include the |this|, but the exprStackSlots won't.
+ // Exception: if there are no arguments, the depths do match.
+ MOZ_ASSERT(IsInvokeOp(op));
+ if (GET_ARGC(pc) > 0) {
+ MOZ_ASSERT(expectedDepth == exprStackSlots + 1);
+ } else {
+ MOZ_ASSERT(expectedDepth == exprStackSlots);
+ }
+ return true;
+ }
+
+ if (mode == ResumeMode::InlinedAccessor) {
+ // Accessors coming out of ion are inlined via a complete lie perpetrated by
+ // the compiler internally. Ion just rearranges the stack, and pretends that
+ // it looked like a call all along.
+ // This means that the depth is actually one *more* than expected by the
+ // interpreter, as there is now a JSFunction, |this| and [arg], rather than
+ // the expected |this| and [arg].
+ // If the inlined accessor is a GetElem operation, the numbers do match, but
+ // that's just because GetElem expects one more item on the stack. Note that
+ // none of that was pushed, but it's still reflected in exprStackSlots.
+ MOZ_ASSERT(IsIonInlinableGetterOrSetterOp(op));
+ if (IsGetElemOp(op)) {
+ MOZ_ASSERT(exprStackSlots == expectedDepth);
+ } else {
+ MOZ_ASSERT(exprStackSlots == expectedDepth + 1);
+ }
+ return true;
+ }
+
+ // In all other cases, the depth must match.
+ MOZ_ASSERT(exprStackSlots == expectedDepth);
+ return true;
+}
+
+bool BaselineStackBuilder::validateFrame() {
+ const uint32_t frameSize = framePushed();
+ blFrame()->setDebugFrameSize(frameSize);
+ JitSpew(JitSpew_BaselineBailouts, " FrameSize=%u", frameSize);
+
+ // debugNumValueSlots() is based on the frame size, do some sanity checks.
+ MOZ_ASSERT(blFrame()->debugNumValueSlots() >= script_->nfixed());
+ MOZ_ASSERT(blFrame()->debugNumValueSlots() <= script_->nslots());
+
+ uint32_t expectedSlots = exprStackSlots();
+ if (resumingInFinallyBlock()) {
+ // If we are resuming in a finally block, we push two extra values on the
+ // stack (the exception, and |throwing|), so the depth at the resume PC
+ // should be the depth at the fault PC plus two.
+ expectedSlots += 2;
+ }
+ return AssertBailoutStackDepth(cx_, script_, pc_, resumeMode(),
+ expectedSlots);
+}
+#endif
+
+void* BaselineStackBuilder::getStubReturnAddress() {
+ const BaselineICFallbackCode& code =
+ cx_->runtime()->jitRuntime()->baselineICFallbackCode();
+
+ if (IsGetPropOp(op_)) {
+ return code.bailoutReturnAddr(BailoutReturnKind::GetProp);
+ }
+ if (IsSetPropOp(op_)) {
+ return code.bailoutReturnAddr(BailoutReturnKind::SetProp);
+ }
+ if (IsGetElemOp(op_)) {
+ return code.bailoutReturnAddr(BailoutReturnKind::GetElem);
+ }
+
+ // This should be a call op of some kind, now.
+ MOZ_ASSERT(IsInvokeOp(op_) && !IsSpreadOp(op_));
+ if (IsConstructOp(op_)) {
+ return code.bailoutReturnAddr(BailoutReturnKind::New);
+ }
+ return code.bailoutReturnAddr(BailoutReturnKind::Call);
+}
+
+static inline jsbytecode* GetNextNonLoopHeadPc(jsbytecode* pc) {
+ JSOp op = JSOp(*pc);
+ switch (op) {
+ case JSOp::Goto:
+ return pc + GET_JUMP_OFFSET(pc);
+
+ case JSOp::LoopHead:
+ case JSOp::Nop:
+ return GetNextPc(pc);
+
+ default:
+ return pc;
+ }
+}
+
+// Returns the pc to resume execution at in Baseline after a bailout.
+jsbytecode* BaselineStackBuilder::getResumePC() {
+ if (resumeAfter()) {
+ return GetNextPc(pc_);
+ }
+
+ // If we are resuming at a LoopHead op, resume at the next op to avoid
+ // a bailout -> enter Ion -> bailout loop with --ion-eager.
+ //
+ // Cycles can cause the loop below to not terminate. Empty loops are one
+ // such example:
+ //
+ // L: loophead
+ // goto L
+ //
+ // We do cycle detection below with the "tortoise and the hare" algorithm.
+ jsbytecode* slowerPc = pc_;
+ jsbytecode* fasterPc = pc_;
+ while (true) {
+ // Advance fasterPc twice as fast as slowerPc.
+ slowerPc = GetNextNonLoopHeadPc(slowerPc);
+ fasterPc = GetNextNonLoopHeadPc(fasterPc);
+ fasterPc = GetNextNonLoopHeadPc(fasterPc);
+
+ // Break on cycles or at the end of goto sequences.
+ if (fasterPc == slowerPc) {
+ break;
+ }
+ }
+
+ return slowerPc;
+}
+
+bool BaselineStackBuilder::isPrologueBailout() {
+ // If we are propagating an exception for debug mode, we will not resume
+ // into baseline code, but instead into HandleExceptionBaseline (i.e.,
+ // never before the prologue).
+ return iter_.pcOffset() == 0 && !iter_.resumeAfter() &&
+ !propagatingIonExceptionForDebugMode();
+}
+
+// Build a baseline stack frame.
+bool BaselineStackBuilder::buildOneFrame() {
+ // Build a baseline frame:
+ // +===============+
+ // | PrevFramePtr | <-- initFrame()
+ // +---------------+
+ // | Baseline | <-- buildBaselineFrame()
+ // | Frame |
+ // +---------------+
+ // | Fixed0 | <-- buildFixedSlots()
+ // +---------------+
+ // | ... |
+ // +---------------+
+ // | FixedF |
+ // +---------------+
+ // | Stack0 | <-- buildExpressionStack() -or- fixupCallerArgs()
+ // +---------------+
+ // | ... |
+ // +---------------+ If we are building the frame in which we will
+ // | StackS | <-- resume, we stop here.
+ // +---------------+ finishLastFrame() sets up the interpreter fields.
+ // . .
+ // . .
+ // . . <-- If there are additional frames inlined into this
+ // | Descr(BLJS) | one, we finish this frame. We generate a stub
+ // +---------------+ frame (and maybe also a rectifier frame) between
+ // | ReturnAddr | this frame and the inlined frame.
+ // +===============+ See: prepareForNextFrame()
+
+ if (!initFrame()) {
+ return false;
+ }
+
+ if (!buildBaselineFrame()) {
+ return false;
+ }
+
+ if (fun_ && !buildArguments()) {
+ return false;
+ }
+
+ if (!buildFixedSlots()) {
+ return false;
+ }
+
+ bool fixedUp = false;
+ RootedValueVector savedCallerArgs(cx_);
+ if (iter_.moreFrames() && !fixUpCallerArgs(&savedCallerArgs, &fixedUp)) {
+ return false;
+ }
+
+ if (!fixedUp) {
+ if (!buildExpressionStack()) {
+ return false;
+ }
+ if (resumingInFinallyBlock() && !buildFinallyException()) {
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ if (!validateFrame()) {
+ return false;
+ }
+#endif
+
+#ifdef JS_JITSPEW
+ const uint32_t pcOff = script_->pcToOffset(pc());
+ JitSpew(JitSpew_BaselineBailouts,
+ " Resuming %s pc offset %d (op %s) (line %u) of %s:%u:%u",
+ resumeAfter() ? "after" : "at", (int)pcOff, CodeName(op_),
+ PCToLineNumber(script_, pc()), script_->filename(), script_->lineno(),
+ script_->column());
+ JitSpew(JitSpew_BaselineBailouts, " Bailout kind: %s",
+ BailoutKindString(bailoutKind()));
+#endif
+
+ // If this was the last inline frame, or we are bailing out to a catch or
+ // finally block in this frame, then unpacking is almost done.
+ if (done()) {
+ return finishLastFrame();
+ }
+
+ // Otherwise, this is an outer frame for an inlined call or
+ // accessor. We will be building an inner frame. Before that,
+ // we must create a stub frame, and potentially a rectifier frame.
+ return prepareForNextFrame(savedCallerArgs);
+}
+
+bool jit::BailoutIonToBaseline(JSContext* cx, JitActivation* activation,
+ const JSJitFrameIter& iter,
+ BaselineBailoutInfo** bailoutInfo,
+ const ExceptionBailoutInfo* excInfo,
+ BailoutReason reason) {
+ MOZ_ASSERT(bailoutInfo != nullptr);
+ MOZ_ASSERT(*bailoutInfo == nullptr);
+ MOZ_ASSERT(iter.isBailoutJS());
+
+ // Caller should have saved the exception while we perform the bailout.
+ MOZ_ASSERT(!cx->isExceptionPending());
+
+ // Ion bailout can fail due to overrecursion and OOM. In such cases we
+ // cannot honor any further Debugger hooks on the frame, and need to
+ // ensure that its Debugger.Frame entry is cleaned up.
+ auto guardRemoveRematerializedFramesFromDebugger =
+ mozilla::MakeScopeExit([&] {
+ activation->removeRematerializedFramesFromDebugger(cx, iter.fp());
+ });
+
+ // Always remove the RInstructionResults from the JitActivation, even in
+ // case of failures as the stack frame is going away after the bailout.
+ auto removeIonFrameRecovery = mozilla::MakeScopeExit(
+ [&] { activation->removeIonFrameRecovery(iter.jsFrame()); });
+
+ // The caller of the top frame must be one of the following:
+ // IonJS - Ion calling into Ion.
+ // BaselineStub - Baseline calling into Ion.
+ // Entry / WasmToJSJit - Interpreter or other (wasm) calling into Ion.
+ // Rectifier - Arguments rectifier calling into Ion.
+ // BaselineJS - Resume'd Baseline, then likely OSR'd into Ion.
+ MOZ_ASSERT(iter.isBailoutJS());
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ FrameType prevFrameType = iter.prevType();
+ MOZ_ASSERT(JSJitFrameIter::isEntry(prevFrameType) ||
+ prevFrameType == FrameType::IonJS ||
+ prevFrameType == FrameType::BaselineStub ||
+ prevFrameType == FrameType::Rectifier ||
+ prevFrameType == FrameType::IonICCall ||
+ prevFrameType == FrameType::BaselineJS);
+#endif
+
+ // All incoming frames are going to look like this:
+ //
+ // +---------------+
+ // | ... |
+ // +---------------+
+ // | Args |
+ // | ... |
+ // +---------------+
+ // | ThisV |
+ // +---------------+
+ // | ActualArgC |
+ // +---------------+
+ // | CalleeToken |
+ // +---------------+
+ // | Descriptor |
+ // +---------------+
+ // | ReturnAddr |
+ // +---------------+
+ // | ||||| | <---- Overwrite starting here.
+ // | ||||| |
+ // | ||||| |
+ // +---------------+
+
+ JitSpew(JitSpew_BaselineBailouts,
+ "Bailing to baseline %s:%u:%u (IonScript=%p) (FrameType=%d)",
+ iter.script()->filename(), iter.script()->lineno(),
+ iter.script()->column(), (void*)iter.ionScript(), (int)prevFrameType);
+
+ if (excInfo) {
+ if (excInfo->catchingException()) {
+ JitSpew(JitSpew_BaselineBailouts, "Resuming in catch or finally block");
+ }
+ if (excInfo->propagatingIonExceptionForDebugMode()) {
+ JitSpew(JitSpew_BaselineBailouts, "Resuming in-place for debug mode");
+ }
+ }
+
+ JitSpew(JitSpew_BaselineBailouts,
+ " Reading from snapshot offset %u size %zu", iter.snapshotOffset(),
+ iter.ionScript()->snapshotsListSize());
+
+ iter.script()->updateJitCodeRaw(cx->runtime());
+
+ // Under a bailout, there is no need to invalidate the frame after
+ // evaluating the recover instruction, as the invalidation is only needed in
+ // cases where the frame is introspected ahead of the bailout.
+ MaybeReadFallback recoverBailout(cx, activation, &iter,
+ MaybeReadFallback::Fallback_DoNothing);
+
+ // Ensure that all value locations are readable from the SnapshotIterator.
+ // Get the RInstructionResults from the JitActivation if the frame got
+ // recovered ahead of the bailout.
+ SnapshotIterator snapIter(iter, activation->bailoutData()->machineState());
+ if (!snapIter.initInstructionResults(recoverBailout)) {
+ return false;
+ }
+
+#ifdef TRACK_SNAPSHOTS
+ snapIter.spewBailingFrom();
+#endif
+
+ BaselineStackBuilder builder(cx, iter, snapIter, excInfo, reason);
+ if (!builder.init()) {
+ return false;
+ }
+
+ JitSpew(JitSpew_BaselineBailouts, " Incoming frame ptr = %p",
+ builder.startFrame());
+ if (iter.maybeCallee()) {
+ JitSpew(JitSpew_BaselineBailouts, " Callee function (%s:%u:%u)",
+ iter.script()->filename(), iter.script()->lineno(),
+ iter.script()->column());
+ } else {
+ JitSpew(JitSpew_BaselineBailouts, " No callee!");
+ }
+
+ if (iter.isConstructing()) {
+ JitSpew(JitSpew_BaselineBailouts, " Constructing!");
+ } else {
+ JitSpew(JitSpew_BaselineBailouts, " Not constructing!");
+ }
+
+ JitSpew(JitSpew_BaselineBailouts, " Restoring frames:");
+
+ while (true) {
+ // Skip recover instructions as they are already recovered by
+ // |initInstructionResults|.
+ snapIter.settleOnFrame();
+
+ JitSpew(JitSpew_BaselineBailouts, " FrameNo %zu", builder.frameNo());
+
+ if (!builder.buildOneFrame()) {
+ MOZ_ASSERT(cx->isExceptionPending());
+ return false;
+ }
+
+ if (builder.done()) {
+ break;
+ }
+
+ builder.nextFrame();
+ }
+ JitSpew(JitSpew_BaselineBailouts, " Done restoring frames");
+
+ BailoutKind bailoutKind = builder.bailoutKind();
+
+ if (!builder.outermostFrameFormals().empty()) {
+ // Set the first frame's formals, see the comment in InitFromBailout.
+ Value* argv = builder.startFrame()->actualArgs();
+ mozilla::PodCopy(argv, builder.outermostFrameFormals().begin(),
+ builder.outermostFrameFormals().length());
+ }
+
+ // Do stack check.
+ bool overRecursed = false;
+ BaselineBailoutInfo* info = builder.info();
+ size_t numBytesToPush = info->copyStackTop - info->copyStackBottom;
+ MOZ_ASSERT((numBytesToPush % sizeof(uintptr_t)) == 0);
+ uint8_t* newsp = info->incomingStack - numBytesToPush;
+#ifdef JS_SIMULATOR
+ if (Simulator::Current()->overRecursed(uintptr_t(newsp))) {
+ overRecursed = true;
+ }
+#else
+ AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.checkWithStackPointerDontReport(cx, newsp)) {
+ overRecursed = true;
+ }
+#endif
+ if (overRecursed) {
+ JitSpew(JitSpew_BaselineBailouts, " Overrecursion check failed!");
+ ReportOverRecursed(cx);
+ return false;
+ }
+
+ // Take the reconstructed baseline stack so it doesn't get freed when builder
+ // destructs.
+ info = builder.takeBuffer();
+ info->numFrames = builder.frameNo() + 1;
+ info->bailoutKind.emplace(bailoutKind);
+ *bailoutInfo = info;
+ guardRemoveRematerializedFramesFromDebugger.release();
+ return true;
+}
+
+static void InvalidateAfterBailout(JSContext* cx, HandleScript outerScript,
+ const char* reason) {
+ // In some cases, the computation of recover instruction can invalidate the
+ // Ion script before we reach the end of the bailout. Thus, if the outer
+ // script no longer have any Ion script attached, then we just skip the
+ // invalidation.
+ //
+ // For example, such case can happen if the template object for an unboxed
+ // objects no longer match the content of its properties (see Bug 1174547)
+ if (!outerScript->hasIonScript()) {
+ JitSpew(JitSpew_BaselineBailouts, "Ion script is already invalidated");
+ return;
+ }
+
+ MOZ_ASSERT(!outerScript->ionScript()->invalidated());
+
+ JitSpew(JitSpew_BaselineBailouts, "Invalidating due to %s", reason);
+ Invalidate(cx, outerScript);
+}
+
+static void HandleLexicalCheckFailure(JSContext* cx, HandleScript outerScript,
+ HandleScript innerScript) {
+ JitSpew(JitSpew_IonBailouts,
+ "Lexical check failure %s:%u:%u, inlined into %s:%u:%u",
+ innerScript->filename(), innerScript->lineno(), innerScript->column(),
+ outerScript->filename(), outerScript->lineno(),
+ outerScript->column());
+
+ if (!innerScript->failedLexicalCheck()) {
+ innerScript->setFailedLexicalCheck();
+ }
+
+ InvalidateAfterBailout(cx, outerScript, "lexical check failure");
+ if (innerScript->hasIonScript()) {
+ Invalidate(cx, innerScript);
+ }
+}
+
+static bool CopyFromRematerializedFrame(JSContext* cx, JitActivation* act,
+ uint8_t* fp, size_t inlineDepth,
+ BaselineFrame* frame) {
+ RematerializedFrame* rematFrame =
+ act->lookupRematerializedFrame(fp, inlineDepth);
+
+ // We might not have rematerialized a frame if the user never requested a
+ // Debugger.Frame for it.
+ if (!rematFrame) {
+ return true;
+ }
+
+ MOZ_ASSERT(rematFrame->script() == frame->script());
+ MOZ_ASSERT(rematFrame->numActualArgs() == frame->numActualArgs());
+
+ frame->setEnvironmentChain(rematFrame->environmentChain());
+
+ if (frame->isFunctionFrame()) {
+ frame->thisArgument() = rematFrame->thisArgument();
+ }
+
+ for (unsigned i = 0; i < frame->numActualArgs(); i++) {
+ frame->argv()[i] = rematFrame->argv()[i];
+ }
+
+ for (size_t i = 0; i < frame->script()->nfixed(); i++) {
+ *frame->valueSlot(i) = rematFrame->locals()[i];
+ }
+
+ frame->setReturnValue(rematFrame->returnValue());
+
+ // Don't copy over the hasCachedSavedFrame bit. The new BaselineFrame we're
+ // building has a different AbstractFramePtr, so it won't be found in the
+ // LiveSavedFrameCache if we look there.
+
+ JitSpew(JitSpew_BaselineBailouts,
+ " Copied from rematerialized frame at (%p,%zu)", fp, inlineDepth);
+
+ // Propagate the debuggee frame flag. For the case where the Debugger did
+ // not rematerialize an Ion frame, the baseline frame has its debuggee
+ // flag set iff its script is considered a debuggee. See the debuggee case
+ // in InitFromBailout.
+ if (rematFrame->isDebuggee()) {
+ frame->setIsDebuggee();
+ return DebugAPI::handleIonBailout(cx, rematFrame, frame);
+ }
+
+ return true;
+}
+
+enum class BailoutAction {
+ InvalidateImmediately,
+ InvalidateIfFrequent,
+ DisableIfFrequent,
+ NoAction
+};
+
+bool jit::FinishBailoutToBaseline(BaselineBailoutInfo* bailoutInfoArg) {
+ JitSpew(JitSpew_BaselineBailouts, " Done restoring frames");
+
+ // Use UniquePtr to free the bailoutInfo before we return.
+ UniquePtr<BaselineBailoutInfo> bailoutInfo(bailoutInfoArg);
+ bailoutInfoArg = nullptr;
+
+ MOZ_DIAGNOSTIC_ASSERT(*bailoutInfo->bailoutKind != BailoutKind::Unreachable);
+
+ JSContext* cx = TlsContext.get();
+ BaselineFrame* topFrame = GetTopBaselineFrame(cx);
+
+ // We have to get rid of the rematerialized frame, whether it is
+ // restored or unwound.
+ uint8_t* incomingStack = bailoutInfo->incomingStack;
+ auto guardRemoveRematerializedFramesFromDebugger =
+ mozilla::MakeScopeExit([&] {
+ JitActivation* act = cx->activation()->asJit();
+ act->removeRematerializedFramesFromDebugger(cx, incomingStack);
+ });
+
+ // Ensure the frame has a call object if it needs one.
+ if (!EnsureHasEnvironmentObjects(cx, topFrame)) {
+ return false;
+ }
+
+ // Create arguments objects for bailed out frames, to maintain the invariant
+ // that script->needsArgsObj() implies frame->hasArgsObj().
+ RootedScript innerScript(cx, nullptr);
+ RootedScript outerScript(cx, nullptr);
+
+ MOZ_ASSERT(cx->currentlyRunningInJit());
+ JSJitFrameIter iter(cx->activation()->asJit());
+ uint8_t* outerFp = nullptr;
+
+ // Iter currently points at the exit frame. Get the previous frame
+ // (which must be a baseline frame), and set it as the last profiling
+ // frame.
+ if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(
+ cx->runtime())) {
+ MOZ_ASSERT(iter.prevType() == FrameType::BaselineJS);
+ JitFrameLayout* fp = reinterpret_cast<JitFrameLayout*>(iter.prevFp());
+ cx->jitActivation->setLastProfilingFrame(fp);
+ }
+
+ uint32_t numFrames = bailoutInfo->numFrames;
+ MOZ_ASSERT(numFrames > 0);
+
+ uint32_t frameno = 0;
+ while (frameno < numFrames) {
+ MOZ_ASSERT(!iter.isIonJS());
+
+ if (iter.isBaselineJS()) {
+ BaselineFrame* frame = iter.baselineFrame();
+ MOZ_ASSERT(frame->script()->hasBaselineScript());
+
+ // If the frame doesn't even have a env chain set yet, then it's resuming
+ // into the the prologue before the env chain is initialized. Any
+ // necessary args object will also be initialized there.
+ if (frame->environmentChain() && frame->script()->needsArgsObj()) {
+ ArgumentsObject* argsObj;
+ if (frame->hasArgsObj()) {
+ argsObj = &frame->argsObj();
+ } else {
+ argsObj = ArgumentsObject::createExpected(cx, frame);
+ if (!argsObj) {
+ return false;
+ }
+ }
+
+ // The arguments is a local binding and needsArgsObj does not
+ // check if it is clobbered. Ensure that the local binding
+ // restored during bailout before storing the arguments object
+ // to the slot.
+ RootedScript script(cx, frame->script());
+ SetFrameArgumentsObject(cx, frame, script, argsObj);
+ }
+
+ if (frameno == 0) {
+ innerScript = frame->script();
+ }
+
+ if (frameno == numFrames - 1) {
+ outerScript = frame->script();
+ outerFp = iter.fp();
+ MOZ_ASSERT(outerFp == incomingStack);
+ }
+
+ frameno++;
+ }
+
+ ++iter;
+ }
+
+ MOZ_ASSERT(innerScript);
+ MOZ_ASSERT(outerScript);
+ MOZ_ASSERT(outerFp);
+
+ // If we rematerialized Ion frames due to debug mode toggling, copy their
+ // values into the baseline frame. We need to do this even when debug mode
+ // is off, as we should respect the mutations made while debug mode was
+ // on.
+ JitActivation* act = cx->activation()->asJit();
+ if (act->hasRematerializedFrame(outerFp)) {
+ JSJitFrameIter iter(act);
+ size_t inlineDepth = numFrames;
+ bool ok = true;
+ while (inlineDepth > 0) {
+ if (iter.isBaselineJS()) {
+ // We must attempt to copy all rematerialized frames over,
+ // even if earlier ones failed, to invoke the proper frame
+ // cleanup in the Debugger.
+ if (!CopyFromRematerializedFrame(cx, act, outerFp, --inlineDepth,
+ iter.baselineFrame())) {
+ ok = false;
+ }
+ }
+ ++iter;
+ }
+
+ if (!ok) {
+ return false;
+ }
+
+ // After copying from all the rematerialized frames, remove them from
+ // the table to keep the table up to date.
+ guardRemoveRematerializedFramesFromDebugger.release();
+ act->removeRematerializedFrame(outerFp);
+ }
+
+ // If we are unwinding for an exception, we need to unwind scopes.
+ // See |SettleOnTryNote|
+ if (bailoutInfo->faultPC) {
+ EnvironmentIter ei(cx, topFrame, bailoutInfo->faultPC);
+ UnwindEnvironment(cx, ei, bailoutInfo->tryPC);
+ }
+
+ // Check for interrupts now because we might miss an interrupt check in JIT
+ // code when resuming in the prologue, after the stack/interrupt check.
+ if (!cx->isExceptionPending()) {
+ if (!CheckForInterrupt(cx)) {
+ return false;
+ }
+ }
+
+ BailoutKind bailoutKind = *bailoutInfo->bailoutKind;
+ JitSpew(JitSpew_BaselineBailouts,
+ " Restored outerScript=(%s:%u:%u,%u) innerScript=(%s:%u:%u,%u) "
+ "(bailoutKind=%u)",
+ outerScript->filename(), outerScript->lineno(), outerScript->column(),
+ outerScript->getWarmUpCount(), innerScript->filename(),
+ innerScript->lineno(), innerScript->column(),
+ innerScript->getWarmUpCount(), (unsigned)bailoutKind);
+
+ BailoutAction action = BailoutAction::InvalidateImmediately;
+ DebugOnly<bool> saveFailedICHash = false;
+ switch (bailoutKind) {
+ case BailoutKind::TranspiledCacheIR:
+ // A transpiled guard failed. If this happens often enough, we will
+ // invalidate and recompile.
+ action = BailoutAction::InvalidateIfFrequent;
+ saveFailedICHash = true;
+ break;
+
+ case BailoutKind::SpeculativePhi:
+ // A value of an unexpected type flowed into a phi.
+ MOZ_ASSERT(!outerScript->hadSpeculativePhiBailout());
+ outerScript->setHadSpeculativePhiBailout();
+ InvalidateAfterBailout(cx, outerScript, "phi specialization failure");
+ break;
+
+ case BailoutKind::TypePolicy:
+ // A conversion inserted by a type policy failed.
+ // We will invalidate and disable recompilation if this happens too often.
+ action = BailoutAction::DisableIfFrequent;
+ break;
+
+ case BailoutKind::LICM:
+ // LICM may cause spurious bailouts by hoisting unreachable
+ // guards past branches. To prevent bailout loops, when an
+ // instruction hoisted by LICM bails out, we update the
+ // IonScript and resume in baseline. If the guard would have
+ // been executed anyway, then we will hit the baseline fallback,
+ // and call noteBaselineFallback. If that does not happen,
+ // then the next time we reach this point, we will disable LICM
+ // for this script.
+ MOZ_ASSERT(!outerScript->hadLICMInvalidation());
+ if (outerScript->hasIonScript()) {
+ switch (outerScript->ionScript()->licmState()) {
+ case IonScript::LICMState::NeverBailed:
+ outerScript->ionScript()->setHadLICMBailout();
+ action = BailoutAction::NoAction;
+ break;
+ case IonScript::LICMState::Bailed:
+ outerScript->setHadLICMInvalidation();
+ InvalidateAfterBailout(cx, outerScript, "LICM failure");
+ break;
+ case IonScript::LICMState::BailedAndHitFallback:
+ // This bailout is not due to LICM. Treat it like a
+ // regular TranspiledCacheIR bailout.
+ action = BailoutAction::InvalidateIfFrequent;
+ break;
+ }
+ }
+ break;
+
+ case BailoutKind::InstructionReordering:
+ // An instruction moved up by instruction reordering bailed out.
+ outerScript->setHadReorderingBailout();
+ action = BailoutAction::InvalidateIfFrequent;
+ break;
+
+ case BailoutKind::HoistBoundsCheck:
+ // An instruction hoisted or generated by tryHoistBoundsCheck bailed out.
+ MOZ_ASSERT(!outerScript->failedBoundsCheck());
+ outerScript->setFailedBoundsCheck();
+ InvalidateAfterBailout(cx, outerScript, "bounds check failure");
+ break;
+
+ case BailoutKind::EagerTruncation:
+ // An eager truncation generated by range analysis bailed out.
+ // To avoid bailout loops, we set a flag to avoid generating
+ // eager truncations next time we recompile.
+ MOZ_ASSERT(!outerScript->hadEagerTruncationBailout());
+ outerScript->setHadEagerTruncationBailout();
+ InvalidateAfterBailout(cx, outerScript, "eager range analysis failure");
+ break;
+
+ case BailoutKind::UnboxFolding:
+ // An unbox that was hoisted to fold with a load bailed out.
+ // To avoid bailout loops, we set a flag to avoid folding
+ // loads with unboxes next time we recompile.
+ MOZ_ASSERT(!outerScript->hadUnboxFoldingBailout());
+ outerScript->setHadUnboxFoldingBailout();
+ InvalidateAfterBailout(cx, outerScript, "unbox folding failure");
+ break;
+
+ case BailoutKind::TooManyArguments:
+ // A funapply or spread call had more than JIT_ARGS_LENGTH_MAX arguments.
+ // We will invalidate and disable recompilation if this happens too often.
+ action = BailoutAction::DisableIfFrequent;
+ break;
+
+ case BailoutKind::DuringVMCall:
+ if (cx->isExceptionPending()) {
+ // We are bailing out to catch an exception. We will invalidate
+ // and disable recompilation if this happens too often.
+ action = BailoutAction::DisableIfFrequent;
+ }
+ break;
+
+ case BailoutKind::Finally:
+ // We are bailing out for a finally block. We will invalidate
+ // and disable recompilation if this happens too often.
+ action = BailoutAction::DisableIfFrequent;
+ break;
+
+ case BailoutKind::Inevitable:
+ case BailoutKind::Debugger:
+ // Do nothing.
+ action = BailoutAction::NoAction;
+ break;
+
+ case BailoutKind::FirstExecution:
+ // We reached an instruction that had not been executed yet at
+ // the time we compiled. If this happens often enough, we will
+ // invalidate and recompile.
+ action = BailoutAction::InvalidateIfFrequent;
+ saveFailedICHash = true;
+ break;
+
+ case BailoutKind::UninitializedLexical:
+ HandleLexicalCheckFailure(cx, outerScript, innerScript);
+ break;
+
+ case BailoutKind::ThrowCheckIsObject:
+ MOZ_ASSERT(!cx->isExceptionPending());
+ return ThrowCheckIsObject(cx, CheckIsObjectKind::IteratorReturn);
+
+ case BailoutKind::IonExceptionDebugMode:
+ // Return false to resume in HandleException with reconstructed
+ // baseline frame.
+ return false;
+
+ case BailoutKind::OnStackInvalidation:
+ // The script has already been invalidated. There is nothing left to do.
+ action = BailoutAction::NoAction;
+ break;
+
+ default:
+ MOZ_CRASH("Unknown bailout kind!");
+ }
+
+#ifdef DEBUG
+ if (MOZ_UNLIKELY(cx->runtime()->jitRuntime()->ionBailAfterEnabled())) {
+ action = BailoutAction::NoAction;
+ }
+#endif
+
+ if (outerScript->hasIonScript()) {
+ IonScript* ionScript = outerScript->ionScript();
+ switch (action) {
+ case BailoutAction::InvalidateImmediately:
+ // The IonScript should already have been invalidated.
+ MOZ_ASSERT(false);
+ break;
+ case BailoutAction::InvalidateIfFrequent:
+ ionScript->incNumFixableBailouts();
+ if (ionScript->shouldInvalidate()) {
+#ifdef DEBUG
+ if (saveFailedICHash && !JitOptions.disableBailoutLoopCheck) {
+ outerScript->jitScript()->setFailedICHash(ionScript->icHash());
+ }
+#endif
+ InvalidateAfterBailout(cx, outerScript, "fixable bailouts");
+ }
+ break;
+ case BailoutAction::DisableIfFrequent:
+ ionScript->incNumUnfixableBailouts();
+ if (ionScript->shouldInvalidateAndDisable()) {
+ InvalidateAfterBailout(cx, outerScript, "unfixable bailouts");
+ outerScript->disableIon();
+ }
+ break;
+ case BailoutAction::NoAction:
+ break;
+ }
+ }
+
+ return true;
+}