| /* -*- 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 "mozilla/ScopeExit.h" |
| #include "mozilla/SizePrintfMacros.h" |
| |
| #include "jsprf.h" |
| #include "jsutil.h" |
| #include "jit/arm/Simulator-arm.h" |
| #include "jit/BaselineIC.h" |
| #include "jit/BaselineJIT.h" |
| #include "jit/CompileInfo.h" |
| #include "jit/JitSpewer.h" |
| #include "jit/mips32/Simulator-mips32.h" |
| #include "jit/mips64/Simulator-mips64.h" |
| #include "jit/Recover.h" |
| #include "jit/RematerializedFrame.h" |
| |
| #include "vm/ArgumentsObject.h" |
| #include "vm/Debugger.h" |
| #include "vm/TraceLogging.h" |
| |
| #include "jsscriptinlines.h" |
| |
| #include "jit/JitFrames-inl.h" |
| |
| using namespace js; |
| using namespace js::jit; |
| |
| // 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 |
| { |
| BaselineBailoutInfo** header_; |
| size_t offset_; |
| bool heap_; |
| |
| public: |
| BufferPointer(BaselineBailoutInfo** header, size_t offset, bool heap) |
| : header_(header), offset_(offset), heap_(heap) |
| { } |
| |
| T* get() const { |
| BaselineBailoutInfo* header = *header_; |
| if (!heap_) |
| return (T*)(header->incomingStack + offset_); |
| |
| uint8_t* p = header->copyStackTop - offset_; |
| MOZ_ASSERT(p >= header->copyStackBottom && p < header->copyStackTop); |
| return (T*)p; |
| } |
| |
| 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. |
| */ |
| struct BaselineStackBuilder |
| { |
| JitFrameIterator& iter_; |
| JitFrameLayout* frame_; |
| |
| static size_t HeaderSize() { |
| return AlignBytes(sizeof(BaselineBailoutInfo), sizeof(void*)); |
| } |
| size_t bufferTotal_; |
| size_t bufferAvail_; |
| size_t bufferUsed_; |
| uint8_t* buffer_; |
| BaselineBailoutInfo* header_; |
| |
| size_t framePushed_; |
| |
| BaselineStackBuilder(JitFrameIterator& iter, size_t initialSize) |
| : iter_(iter), |
| frame_(static_cast<JitFrameLayout*>(iter.current())), |
| bufferTotal_(initialSize), |
| bufferAvail_(0), |
| bufferUsed_(0), |
| buffer_(nullptr), |
| header_(nullptr), |
| framePushed_(0) |
| { |
| MOZ_ASSERT(bufferTotal_ >= HeaderSize()); |
| MOZ_ASSERT(iter.isBailoutJS()); |
| } |
| |
| ~BaselineStackBuilder() { |
| js_free(buffer_); |
| } |
| |
| bool init() { |
| MOZ_ASSERT(!buffer_); |
| MOZ_ASSERT(bufferUsed_ == 0); |
| buffer_ = reinterpret_cast<uint8_t*>(js_calloc(bufferTotal_)); |
| if (!buffer_) |
| return false; |
| bufferAvail_ = bufferTotal_ - HeaderSize(); |
| bufferUsed_ = 0; |
| |
| header_ = reinterpret_cast<BaselineBailoutInfo*>(buffer_); |
| header_->incomingStack = reinterpret_cast<uint8_t*>(frame_); |
| header_->copyStackTop = buffer_ + bufferTotal_; |
| header_->copyStackBottom = header_->copyStackTop; |
| header_->setR0 = 0; |
| header_->valueR0 = UndefinedValue(); |
| header_->setR1 = 0; |
| header_->valueR1 = UndefinedValue(); |
| header_->resumeFramePtr = nullptr; |
| header_->resumeAddr = nullptr; |
| header_->resumePC = nullptr; |
| header_->monitorStub = nullptr; |
| header_->numFrames = 0; |
| return true; |
| } |
| |
| bool enlarge() { |
| MOZ_ASSERT(buffer_ != nullptr); |
| if (bufferTotal_ & mozilla::tl::MulOverflowMask<2>::value) |
| return false; |
| size_t newSize = bufferTotal_ * 2; |
| uint8_t* newBuffer = reinterpret_cast<uint8_t*>(js_calloc(newSize)); |
| if (!newBuffer) |
| return false; |
| memcpy((newBuffer + newSize) - bufferUsed_, header_->copyStackBottom, bufferUsed_); |
| memcpy(newBuffer, header_, sizeof(BaselineBailoutInfo)); |
| js_free(buffer_); |
| buffer_ = newBuffer; |
| bufferTotal_ = newSize; |
| bufferAvail_ = newSize - (HeaderSize() + bufferUsed_); |
| |
| header_ = reinterpret_cast<BaselineBailoutInfo*>(buffer_); |
| header_->copyStackTop = buffer_ + bufferTotal_; |
| header_->copyStackBottom = header_->copyStackTop - bufferUsed_; |
| return true; |
| } |
| |
| BaselineBailoutInfo* info() { |
| MOZ_ASSERT(header_ == reinterpret_cast<BaselineBailoutInfo*>(buffer_)); |
| return header_; |
| } |
| |
| BaselineBailoutInfo* takeBuffer() { |
| MOZ_ASSERT(header_ == reinterpret_cast<BaselineBailoutInfo*>(buffer_)); |
| buffer_ = nullptr; |
| return header_; |
| } |
| |
| void resetFramePushed() { |
| framePushed_ = 0; |
| } |
| |
| size_t framePushed() const { |
| return framePushed_; |
| } |
| |
| 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> |
| bool write(const T& t) { |
| if (!subtract(sizeof(T))) |
| return false; |
| memcpy(header_->copyStackBottom, &t, sizeof(T)); |
| return true; |
| } |
| |
| template <typename T> |
| 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; |
| } |
| |
| 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 %08x", |
| header_->copyStackBottom, virtualPointerAtStackOffset(0), info, w); |
| } else { |
| JitSpew(JitSpew_BaselineBailouts, |
| " WRITE_WRD %p/%p %-15s %016llx", |
| header_->copyStackBottom, virtualPointerAtStackOffset(0), info, w); |
| } |
| } |
| return true; |
| } |
| |
| bool writeValue(Value val, const char* info) { |
| if (!write<Value>(val)) |
| return false; |
| if (info) { |
| JitSpew(JitSpew_BaselineBailouts, |
| " WRITE_VAL %p/%p %-15s %016llx", |
| header_->copyStackBottom, virtualPointerAtStackOffset(0), info, |
| *((uint64_t*) &val)); |
| } |
| return true; |
| } |
| |
| 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; |
| } |
| |
| Value popValue() { |
| MOZ_ASSERT(bufferUsed_ >= sizeof(Value)); |
| MOZ_ASSERT(framePushed_ >= sizeof(Value)); |
| bufferAvail_ += sizeof(Value); |
| bufferUsed_ -= sizeof(Value); |
| framePushed_ -= sizeof(Value); |
| Value result = *((Value*) header_->copyStackBottom); |
| header_->copyStackBottom += sizeof(Value); |
| return result; |
| } |
| |
| void popValueInto(PCMappingSlotInfo::SlotLocation loc) { |
| MOZ_ASSERT(PCMappingSlotInfo::ValidSlotLocation(loc)); |
| switch(loc) { |
| case PCMappingSlotInfo::SlotInR0: |
| header_->setR0 = 1; |
| header_->valueR0 = popValue(); |
| break; |
| case PCMappingSlotInfo::SlotInR1: |
| header_->setR1 = 1; |
| header_->valueR1 = popValue(); |
| break; |
| default: |
| MOZ_ASSERT(loc == PCMappingSlotInfo::SlotIgnore); |
| popValue(); |
| break; |
| } |
| } |
| |
| void setResumeFramePtr(void* resumeFramePtr) { |
| header_->resumeFramePtr = resumeFramePtr; |
| } |
| |
| void setResumeAddr(void* resumeAddr) { |
| header_->resumeAddr = resumeAddr; |
| } |
| |
| void setResumePC(jsbytecode* pc) { |
| header_->resumePC = pc; |
| } |
| |
| void setMonitorStub(ICStub* stub) { |
| header_->monitorStub = stub; |
| } |
| |
| 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_); |
| } |
| |
| inline JitFrameLayout* startFrame() { |
| return frame_; |
| } |
| |
| BufferPointer<JitFrameLayout> topFrameAddress() { |
| return pointerAtStackOffset<JitFrameLayout>(0); |
| } |
| |
| // |
| // This method should only be called when the builder is in a state where it is |
| // starting to construct the stack frame for the next callee. This means that |
| // the lowest value on the constructed stack is the return address for the previous |
| // caller frame. |
| // |
| // This method is used to compute the value of the frame pointer (e.g. ebp on x86) |
| // that would have been saved by the baseline jitcode when it was entered. In some |
| // cases, this value can be bogus since we can ensure that the caller would have saved |
| // it anyway. |
| // |
| void* calculatePrevFramePtr() { |
| // Get the incoming frame. |
| BufferPointer<JitFrameLayout> topFrame = topFrameAddress(); |
| FrameType type = topFrame->prevType(); |
| |
| // For IonJS, IonAccessorIC and Entry frames, the "saved" frame pointer |
| // in the baseline frame is meaningless, since Ion saves all registers |
| // before calling other ion frames, and the entry frame saves all |
| // registers too. |
| if (type == JitFrame_IonJS || type == JitFrame_Entry || type == JitFrame_IonAccessorIC) |
| return nullptr; |
| |
| // BaselineStub - Baseline calling into Ion. |
| // PrevFramePtr needs to point to the BaselineStubFrame's saved frame pointer. |
| // STACK_START_ADDR + JitFrameLayout::Size() + PREV_FRAME_SIZE |
| // - BaselineStubFrameLayout::reverseOffsetOfSavedFramePtr() |
| if (type == JitFrame_BaselineStub) { |
| size_t offset = JitFrameLayout::Size() + topFrame->prevFrameLocalSize() + |
| BaselineStubFrameLayout::reverseOffsetOfSavedFramePtr(); |
| return virtualPointerAtStackOffset(offset); |
| } |
| |
| MOZ_ASSERT(type == JitFrame_Rectifier); |
| // Rectifier - behaviour depends on the frame preceding the rectifier frame, and |
| // whether the arch is x86 or not. The x86 rectifier frame saves the frame pointer, |
| // so we can calculate it directly. For other archs, the previous frame pointer |
| // is stored on the stack in the frame that precedes the rectifier frame. |
| size_t priorOffset = JitFrameLayout::Size() + topFrame->prevFrameLocalSize(); |
| #if defined(JS_CODEGEN_X86) |
| // On X86, the FramePointer is pushed as the first value in the Rectifier frame. |
| MOZ_ASSERT(BaselineFrameReg == FramePointer); |
| priorOffset -= sizeof(void*); |
| return virtualPointerAtStackOffset(priorOffset); |
| #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \ |
| defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) || \ |
| defined(JS_CODEGEN_X64) |
| // On X64, ARM, ARM64, and MIPS, the frame pointer save location depends on |
| // the caller of the rectifier frame. |
| BufferPointer<RectifierFrameLayout> priorFrame = |
| pointerAtStackOffset<RectifierFrameLayout>(priorOffset); |
| FrameType priorType = priorFrame->prevType(); |
| MOZ_ASSERT(priorType == JitFrame_IonJS || priorType == JitFrame_BaselineStub); |
| |
| // If the frame preceding the rectifier is an IonJS frame, then once again |
| // the frame pointer does not matter. |
| if (priorType == JitFrame_IonJS) |
| return nullptr; |
| |
| // Otherwise, the frame preceding the rectifier is a BaselineStub frame. |
| // let X = STACK_START_ADDR + JitFrameLayout::Size() + PREV_FRAME_SIZE |
| // X + RectifierFrameLayout::Size() |
| // + ((RectifierFrameLayout*) X)->prevFrameLocalSize() |
| // - BaselineStubFrameLayout::reverseOffsetOfSavedFramePtr() |
| size_t extraOffset = RectifierFrameLayout::Size() + priorFrame->prevFrameLocalSize() + |
| BaselineStubFrameLayout::reverseOffsetOfSavedFramePtr(); |
| return virtualPointerAtStackOffset(priorOffset + extraOffset); |
| #elif defined(JS_CODEGEN_NONE) |
| MOZ_CRASH(); |
| #else |
| # error "Bad architecture!" |
| #endif |
| } |
| }; |
| |
| // Ensure that all value locations are readable from the SnapshotIterator. |
| // Remove RInstructionResults from the JitActivation if the frame got recovered |
| // ahead of the bailout. |
| class SnapshotIteratorForBailout : public SnapshotIterator |
| { |
| JitActivation* activation_; |
| JitFrameIterator& iter_; |
| |
| public: |
| SnapshotIteratorForBailout(JitActivation* activation, JitFrameIterator& iter) |
| : SnapshotIterator(iter, activation->bailoutData()->machineState()), |
| activation_(activation), |
| iter_(iter) |
| { |
| MOZ_ASSERT(iter.isBailoutJS()); |
| } |
| |
| ~SnapshotIteratorForBailout() { |
| // The bailout is complete, we no longer need the recover instruction |
| // results. |
| activation_->removeIonFrameRecovery(fp_); |
| } |
| |
| // Take previously computed result out of the activation, or compute the |
| // results of all recover instructions contained in the snapshot. |
| bool init(JSContext* cx) { |
| |
| // Under a bailout, there is no need to invalidate the frame after |
| // evaluating the recover instruction, as the invalidation is only |
| // needed to cause of the frame which has been introspected. |
| MaybeReadFallback recoverBailout(cx, activation_, &iter_, MaybeReadFallback::Fallback_DoNothing); |
| return initInstructionResults(recoverBailout); |
| } |
| }; |
| |
| #ifdef DEBUG |
| static inline bool |
| IsInlinableFallback(ICFallbackStub* icEntry) |
| { |
| return icEntry->isCall_Fallback() || icEntry->isGetProp_Fallback() || |
| icEntry->isSetProp_Fallback(); |
| } |
| #endif |
| |
| static inline void* |
| GetStubReturnAddress(JSContext* cx, jsbytecode* pc) |
| { |
| if (IsGetPropPC(pc)) |
| return cx->compartment()->jitCompartment()->baselineGetPropReturnAddr(); |
| if (IsSetPropPC(pc)) |
| return cx->compartment()->jitCompartment()->baselineSetPropReturnAddr(); |
| // This should be a call op of some kind, now. |
| MOZ_ASSERT(IsCallPC(pc)); |
| return cx->compartment()->jitCompartment()->baselineCallReturnAddr(JSOp(*pc) == JSOP_NEW); |
| } |
| |
| static inline jsbytecode* |
| GetNextNonLoopEntryPc(jsbytecode* pc) |
| { |
| JSOp op = JSOp(*pc); |
| if (op == JSOP_GOTO) |
| return pc + GET_JUMP_OFFSET(pc); |
| if (op == JSOP_LOOPENTRY || op == JSOP_NOP || op == JSOP_LOOPHEAD) |
| return GetNextPc(pc); |
| return pc; |
| } |
| |
| static bool |
| HasLiveIteratorAtStackDepth(JSScript* script, jsbytecode* pc, uint32_t stackDepth) |
| { |
| if (!script->hasTrynotes()) |
| return false; |
| |
| JSTryNote* tn = script->trynotes()->vector; |
| JSTryNote* tnEnd = tn + script->trynotes()->length; |
| uint32_t pcOffset = uint32_t(pc - script->main()); |
| for (; tn != tnEnd; ++tn) { |
| if (pcOffset < tn->start) |
| continue; |
| if (pcOffset >= tn->start + tn->length) |
| continue; |
| |
| // For-in loops have only the iterator on stack. |
| if (tn->kind == JSTRY_FOR_IN && stackDepth == tn->stackDepth) |
| return true; |
| |
| // For-of loops have both the iterator and the result object on |
| // stack. The iterator is below the result object. |
| if (tn->kind == JSTRY_FOR_OF && stackDepth == tn->stackDepth - 1) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // For every inline frame, we write out the following data: |
| // |
| // | ... | |
| // +---------------+ |
| // | Descr(???) | --- Descr size here is (PREV_FRAME_SIZE) |
| // +---------------+ |
| // | ReturnAddr | |
| // -- +===============+ --- OVERWRITE STARTS HERE (START_STACK_ADDR) |
| // | | PrevFramePtr | |
| // | +-> +---------------+ |
| // | | | Baseline | |
| // | | | Frame | |
| // | | +---------------+ |
| // | | | Fixed0 | |
| // | | +---------------+ |
| // +--< | | ... | |
| // | | | +---------------+ |
| // | | | | FixedF | |
| // | | | +---------------+ |
| // | | | | Stack0 | |
| // | | | +---------------+ |
| // | | | | ... | |
| // | | | +---------------+ |
| // | | | | StackS | |
| // | -- | +---------------+ --- IF NOT LAST INLINE FRAME, |
| // +------------| Descr(BLJS) | --- CALLING INFO STARTS HERE |
| // | +---------------+ |
| // | | ReturnAddr | <-- return into main jitcode after IC |
| // -- | +===============+ |
| // | | | StubPtr | |
| // | | +---------------+ |
| // | +---| FramePtr | |
| // | +---------------+ --- The inlined frame might OSR in Ion |
| // | | Padding? | --- Thus the return address should be aligned. |
| // | +---------------+ |
| // +--< | ArgA | |
| // | | +---------------+ |
| // | | | ... | |
| // | | +---------------+ |
| // | | | Arg0 | |
| // | | +---------------+ |
| // | | | ThisV | |
| // | -- +---------------+ |
| // | | ActualArgC | |
| // | +---------------+ |
| // | | CalleeToken | |
| // | +---------------+ |
| // +------------| Descr(BLStub) | |
| // +---------------+ |
| // | ReturnAddr | <-- return into ICCall_Scripted IC |
| // -- +===============+ --- IF CALLEE FORMAL ARGS > ActualArgC |
| // | | Padding? | |
| // | +---------------+ |
| // | | UndefinedU | |
| // | +---------------+ |
| // | | ... | |
| // | +---------------+ |
| // | | Undefined0 | |
| // +--< +---------------+ |
| // | | | ArgA | |
| // | | +---------------+ |
| // | | | ... | |
| // | | +---------------+ |
| // | | | Arg0 | |
| // | | +---------------+ |
| // | | | ThisV | |
| // | -- +---------------+ |
| // | | ActualArgC | |
| // | +---------------+ |
| // | | CalleeToken | |
| // | +---------------+ |
| // +------------| Descr(Rect) | |
| // +---------------+ |
| // | ReturnAddr | <-- return into ArgumentsRectifier after call |
| // +===============+ |
| // |
| static bool |
| InitFromBailout(JSContext* cx, HandleScript caller, jsbytecode* callerPC, |
| HandleFunction fun, HandleScript script, IonScript* ionScript, |
| SnapshotIterator& iter, bool invalidate, BaselineStackBuilder& builder, |
| AutoValueVector& startFrameFormals, MutableHandleFunction nextCallee, |
| jsbytecode** callPC, const ExceptionBailoutInfo* excInfo) |
| { |
| // The Baseline frames we will reconstruct on the heap are not rooted, so GC |
| // must be suppressed here. |
| MOZ_ASSERT(cx->mainThread().suppressGC); |
| |
| MOZ_ASSERT(script->hasBaselineScript()); |
| |
| // Are we catching an exception? |
| bool catchingException = excInfo && excInfo->catchingException(); |
| |
| // 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. |
| uint32_t exprStackSlots; |
| if (catchingException) |
| exprStackSlots = excInfo->numExprSlots(); |
| else |
| exprStackSlots = iter.numAllocations() - (script->nfixed() + CountArgSlots(script, fun)); |
| |
| builder.resetFramePushed(); |
| |
| // Build first baseline frame: |
| // +===============+ |
| // | PrevFramePtr | |
| // +---------------+ |
| // | Baseline | |
| // | Frame | |
| // +---------------+ |
| // | Fixed0 | |
| // +---------------+ |
| // | ... | |
| // +---------------+ |
| // | FixedF | |
| // +---------------+ |
| // | Stack0 | |
| // +---------------+ |
| // | ... | |
| // +---------------+ |
| // | StackS | |
| // +---------------+ --- IF NOT LAST INLINE FRAME, |
| // | Descr(BLJS) | --- CALLING INFO STARTS HERE |
| // +---------------+ |
| // | ReturnAddr | <-- return into main jitcode after IC |
| // +===============+ |
| |
| JitSpew(JitSpew_BaselineBailouts, " Unpacking %s:%d", script->filename(), script->lineno()); |
| JitSpew(JitSpew_BaselineBailouts, " [BASELINE-JS FRAME]"); |
| |
| // Calculate and write the previous frame pointer value. |
| // 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. |
| void* prevFramePtr = builder.calculatePrevFramePtr(); |
| if (!builder.writePtr(prevFramePtr, "PrevFramePtr")) |
| return false; |
| prevFramePtr = builder.virtualPointerAtStackOffset(0); |
| |
| // Write struct BaselineFrame. |
| if (!builder.subtract(BaselineFrame::Size(), "BaselineFrame")) |
| return false; |
| BufferPointer<BaselineFrame> blFrame = builder.pointerAtStackOffset<BaselineFrame>(0); |
| |
| uint32_t flags = 0; |
| |
| // 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; |
| |
| // Initialize BaselineFrame's scopeChain and argsObj |
| JSObject* scopeChain = nullptr; |
| Value returnValue; |
| ArgumentsObject* argsObj = nullptr; |
| BailoutKind bailoutKind = iter.bailoutKind(); |
| if (bailoutKind == Bailout_ArgumentCheck) { |
| // Temporary hack -- skip the (unused) scopeChain, because it could be |
| // bogus (we can fail before the scope chain slot is set). Strip the |
| // hasScopeChain flag and this will be fixed up later in |FinishBailoutToBaseline|, |
| // which calls |EnsureHasScopeObjects|. |
| JitSpew(JitSpew_BaselineBailouts, " Bailout_ArgumentCheck! (no valid scopeChain)"); |
| iter.skip(); |
| |
| // skip |return value| |
| iter.skip(); |
| returnValue = UndefinedValue(); |
| |
| // Scripts with |argumentsHasVarBinding| have an extra slot. |
| if (script->argumentsHasVarBinding()) { |
| JitSpew(JitSpew_BaselineBailouts, |
| " Bailout_ArgumentCheck for script with argumentsHasVarBinding!" |
| "Using empty arguments object"); |
| iter.skip(); |
| } |
| } else { |
| Value v = iter.read(); |
| if (v.isObject()) { |
| scopeChain = &v.toObject(); |
| if (fun && fun->needsCallObject()) |
| flags |= BaselineFrame::HAS_CALL_OBJ; |
| } else { |
| MOZ_ASSERT(v.isUndefined() || v.isMagic(JS_OPTIMIZED_OUT)); |
| |
| // Get scope chain from function or script. |
| if (fun) { |
| // If pcOffset == 0, we may have to push a new call object, so |
| // we leave scopeChain nullptr and enter baseline code before |
| // the prologue. |
| // |
| // If we are propagating an exception for debug mode, we will |
| // not resume into baseline code, but instead into |
| // HandleExceptionBaseline, so *do* set the scope chain here. |
| if (iter.pcOffset() != 0 || iter.resumeAfter() || |
| (excInfo && excInfo->propagatingIonExceptionForDebugMode())) |
| { |
| scopeChain = fun->environment(); |
| } |
| } else if (script->module()) { |
| scopeChain = script->module()->environment(); |
| } else { |
| // For global scripts without a non-syntactic scope the scope |
| // chain is the script's global lexical scope (Ion does 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 scope chain in R1 for |
| // eval and global scripts. |
| MOZ_ASSERT(!script->isForEval()); |
| MOZ_ASSERT(!script->hasNonSyntacticScope()); |
| scopeChain = &(script->global().lexicalScope()); |
| } |
| } |
| |
| // Make sure to add HAS_RVAL to flags here because setFlags() below |
| // will clobber it. |
| returnValue = iter.read(); |
| flags |= BaselineFrame::HAS_RVAL; |
| |
| // If script maybe has an arguments object, the third slot will hold it. |
| if (script->argumentsHasVarBinding()) { |
| v = iter.read(); |
| MOZ_ASSERT(v.isObject() || v.isUndefined() || v.isMagic(JS_OPTIMIZED_OUT)); |
| if (v.isObject()) |
| argsObj = &v.toObject().as<ArgumentsObject>(); |
| } |
| } |
| JitSpew(JitSpew_BaselineBailouts, " ScopeChain=%p", scopeChain); |
| blFrame->setScopeChain(scopeChain); |
| JitSpew(JitSpew_BaselineBailouts, " ReturnValue=%016llx", *((uint64_t*) &returnValue)); |
| blFrame->setReturnValue(returnValue); |
| |
| // Do not need to initialize scratchValue field in BaselineFrame. |
| blFrame->setFlags(flags); |
| |
| // initArgsObjUnchecked modifies the frame's flags, so call it after setFlags. |
| if (argsObj) |
| blFrame->initArgsObjUnchecked(*argsObj); |
| |
| if (fun) { |
| // The unpacked thisv and arguments should overwrite the pushed args present |
| // in the calling frame. |
| Value thisv = iter.read(); |
| JitSpew(JitSpew_BaselineBailouts, " Is function!"); |
| JitSpew(JitSpew_BaselineBailouts, " thisv=%016llx", *((uint64_t*) &thisv)); |
| |
| size_t thisvOffset = builder.framePushed() + JitFrameLayout::offsetOfThis(); |
| *builder.valuePointerAtStackOffset(thisvOffset) = thisv; |
| |
| MOZ_ASSERT(iter.numAllocations() >= CountArgSlots(script, fun)); |
| JitSpew(JitSpew_BaselineBailouts, " frame slots %u, nargs %u, nfixed %u", |
| iter.numAllocations(), fun->nargs(), script->nfixed()); |
| |
| if (!callerPC) { |
| // This is the first frame. Store the formals in a Vector until we |
| // are done. 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. |
| MOZ_ASSERT(startFrameFormals.empty()); |
| if (!startFrameFormals.resize(fun->nargs())) |
| return false; |
| } |
| |
| for (uint32_t i = 0; i < fun->nargs(); i++) { |
| Value arg = iter.read(); |
| JitSpew(JitSpew_BaselineBailouts, " arg %d = %016llx", |
| (int) i, *((uint64_t*) &arg)); |
| if (callerPC) { |
| size_t argOffset = builder.framePushed() + JitFrameLayout::offsetOfActualArg(i); |
| *builder.valuePointerAtStackOffset(argOffset) = arg; |
| } else { |
| startFrameFormals[i].set(arg); |
| } |
| } |
| } |
| |
| for (uint32_t i = 0; i < script->nfixed(); i++) { |
| Value slot = iter.read(); |
| if (!builder.writeValue(slot, "FixedValue")) |
| return false; |
| } |
| |
| // Get the pc. If we are handling an exception, resume at the pc of the |
| // catch or finally block. |
| jsbytecode* pc = catchingException ? excInfo->resumePC() : script->offsetToPC(iter.pcOffset()); |
| bool resumeAfter = catchingException ? false : iter.resumeAfter(); |
| |
| // When pgo is enabled, increment the counter of the block in which we |
| // resume, as Ion does not keep track of the code coverage. |
| // |
| // We need to do that when pgo is enabled, as after a specific number of |
| // FirstExecution bailouts, we invalidate and recompile the script with |
| // IonMonkey. Failing to increment the counter of the current basic block |
| // might lead to repeated bailouts and invalidations. |
| if (!JitOptions.disablePgo && script->hasScriptCounts()) |
| script->incHitCount(pc); |
| |
| JSOp op = JSOp(*pc); |
| |
| // Fixup inlined JSOP_FUNCALL, JSOP_FUNAPPLY, and accessors on the caller side. |
| // On the caller side this must represent like the function wasn't inlined. |
| uint32_t pushedSlots = 0; |
| AutoValueVector savedCallerArgs(cx); |
| bool needToSaveArgs = op == JSOP_FUNAPPLY || IsGetPropPC(pc) || IsSetPropPC(pc); |
| if (iter.moreFrames() && (op == JSOP_FUNCALL || needToSaveArgs)) |
| { |
| uint32_t inlined_args = 0; |
| if (op == JSOP_FUNCALL) |
| inlined_args = 2 + GET_ARGC(pc) - 1; |
| else if (op == JSOP_FUNAPPLY) |
| inlined_args = 2 + blFrame->numActualArgs(); |
| else |
| inlined_args = 2 + IsSetPropPC(pc); |
| |
| MOZ_ASSERT(exprStackSlots >= inlined_args); |
| pushedSlots = exprStackSlots - inlined_args; |
| |
| JitSpew(JitSpew_BaselineBailouts, |
| " pushing %u expression stack slots before fixup", |
| pushedSlots); |
| for (uint32_t i = 0; i < pushedSlots; i++) { |
| Value v = iter.read(); |
| if (!builder.writeValue(v, "StackValue")) |
| return false; |
| } |
| |
| if (op == JSOP_FUNCALL) { |
| // When funcall got inlined and the native js_fun_call was bypassed, |
| // the stack state is incorrect. To restore correctly it must look like |
| // js_fun_call was actually called. This means transforming the stack |
| // from |target, this, args| to |js_fun_call, target, this, args| |
| // The js_fun_call is never read, so just pushing undefined now. |
| JitSpew(JitSpew_BaselineBailouts, " pushing undefined to fixup funcall"); |
| if (!builder.writeValue(UndefinedValue(), "StackValue")) |
| return false; |
| } |
| |
| if (needToSaveArgs) { |
| // When an accessor is inlined, the whole thing is a lie. There |
| // should never have been a call there. Fix the caller's stack to |
| // forget it ever happened. |
| |
| // When funapply gets inlined we take all arguments out of the |
| // arguments array. So the stack state is incorrect. To restore |
| // correctly it must look like js_fun_apply was actually called. |
| // This means transforming the stack from |target, this, arg1, ...| |
| // to |js_fun_apply, target, this, argObject|. |
| // Since the information is never read, we can just push undefined |
| // for all values. |
| if (op == JSOP_FUNAPPLY) { |
| JitSpew(JitSpew_BaselineBailouts, " pushing 4x undefined to fixup funapply"); |
| if (!builder.writeValue(UndefinedValue(), "StackValue")) |
| return false; |
| if (!builder.writeValue(UndefinedValue(), "StackValue")) |
| return false; |
| if (!builder.writeValue(UndefinedValue(), "StackValue")) |
| return false; |
| if (!builder.writeValue(UndefinedValue(), "StackValue")) |
| return false; |
| } |
| // Save the actual arguments. They are needed on the callee side |
| // as the arguments. Else we can't recover them. |
| if (!savedCallerArgs.resize(inlined_args)) |
| return false; |
| for (uint32_t i = 0; i < inlined_args; i++) |
| savedCallerArgs[i].set(iter.read()); |
| |
| if (IsSetPropPC(pc)) { |
| // We would love to just save all the arguments and leave them |
| // in the stub frame pushed below, but we will lose the inital |
| // argument which the function was called with, which we must |
| // return to the caller, even if the setter internally modifies |
| // its arguments. Stash the initial argument on the stack, to be |
| // later retrieved by the SetProp_Fallback stub. |
| Value initialArg = savedCallerArgs[inlined_args - 1]; |
| JitSpew(JitSpew_BaselineBailouts, " pushing setter's initial argument"); |
| if (!builder.writeValue(initialArg, "StackValue")) |
| return false; |
| } |
| pushedSlots = exprStackSlots; |
| } |
| } |
| |
| JitSpew(JitSpew_BaselineBailouts, " pushing %u expression stack slots", |
| exprStackSlots - pushedSlots); |
| for (uint32_t i = pushedSlots; i < exprStackSlots; i++) { |
| Value v; |
| |
| if (!iter.moreFrames() && i == exprStackSlots - 1 && |
| cx->runtime()->jitRuntime()->hasIonReturnOverride()) |
| { |
| // If coming from an invalidation bailout, and this is the topmost |
| // value, and a value override has been specified, don't read from the |
| // iterator. Otherwise, we risk using a garbage value. |
| MOZ_ASSERT(invalidate); |
| iter.skip(); |
| JitSpew(JitSpew_BaselineBailouts, " [Return Override]"); |
| v = cx->runtime()->jitRuntime()->takeIonReturnOverride(); |
| } else if (excInfo && excInfo->propagatingIonExceptionForDebugMode()) { |
| // 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 can't drop |
| // iterators, however, so read them out. They will be closed by |
| // HandleExceptionBaseline. |
| MOZ_ASSERT(cx->compartment()->isDebuggee()); |
| if (iter.moreFrames() || HasLiveIteratorAtStackDepth(script, pc, i + 1)) { |
| v = iter.read(); |
| } else { |
| iter.skip(); |
| v = MagicValue(JS_OPTIMIZED_OUT); |
| } |
| } else { |
| v = iter.read(); |
| } |
| if (!builder.writeValue(v, "StackValue")) |
| return false; |
| } |
| |
| // BaselineFrame::frameSize is the size of everything pushed since |
| // the builder.resetFramePushed() call. |
| uint32_t frameSize = builder.framePushed(); |
| blFrame->setFrameSize(frameSize); |
| JitSpew(JitSpew_BaselineBailouts, " FrameSize=%u", frameSize); |
| |
| // numValueSlots() is based on the frame size, do some sanity checks. |
| MOZ_ASSERT(blFrame->numValueSlots() >= script->nfixed()); |
| MOZ_ASSERT(blFrame->numValueSlots() <= script->nslots()); |
| |
| // If we are resuming at a LOOPENTRY op, resume at the next op to avoid |
| // a bailout -> enter Ion -> bailout loop with --ion-eager. See also |
| // ThunkToInterpreter. |
| // |
| // The algorithm below is the "tortoise and the hare" algorithm. See bug |
| // 994444 for more explanation. |
| if (!resumeAfter) { |
| jsbytecode* fasterPc = pc; |
| while (true) { |
| pc = GetNextNonLoopEntryPc(pc); |
| fasterPc = GetNextNonLoopEntryPc(GetNextNonLoopEntryPc(fasterPc)); |
| if (fasterPc == pc) |
| break; |
| } |
| op = JSOp(*pc); |
| } |
| |
| uint32_t pcOff = script->pcToOffset(pc); |
| bool isCall = IsCallPC(pc); |
| BaselineScript* baselineScript = script->baselineScript(); |
| |
| #ifdef DEBUG |
| uint32_t expectedDepth; |
| bool reachablePC; |
| if (!ReconstructStackDepth(cx, script, resumeAfter ? GetNextPc(pc) : pc, &expectedDepth, &reachablePC)) |
| return false; |
| |
| if (reachablePC) { |
| if (op != JSOP_FUNAPPLY || !iter.moreFrames() || resumeAfter) { |
| if (op == JSOP_FUNCALL) { |
| // For fun.call(this, ...); the reconstructStackDepth will |
| // include the this. When inlining that is not included. |
| // So the exprStackSlots will be one less. |
| MOZ_ASSERT(expectedDepth - exprStackSlots <= 1); |
| } else if (iter.moreFrames() && (IsGetPropPC(pc) || IsSetPropPC(pc))) { |
| // 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] |
| // Note that none of that was pushed, but it's still reflected |
| // in exprStackSlots. |
| MOZ_ASSERT(exprStackSlots - expectedDepth == 1); |
| } else { |
| // For fun.apply({}, arguments) the reconstructStackDepth will |
| // have stackdepth 4, but it could be that we inlined the |
| // funapply. In that case exprStackSlots, will have the real |
| // arguments in the slots and not be 4. |
| MOZ_ASSERT(exprStackSlots == expectedDepth); |
| } |
| } |
| } |
| #endif |
| |
| #ifdef JS_JITSPEW |
| JitSpew(JitSpew_BaselineBailouts, " Resuming %s pc offset %d (op %s) (line %d) of %s:%" PRIuSIZE, |
| resumeAfter ? "after" : "at", (int) pcOff, CodeName[op], |
| PCToLineNumber(script, pc), script->filename(), script->lineno()); |
| JitSpew(JitSpew_BaselineBailouts, " Bailout kind: %s", |
| BailoutKindString(bailoutKind)); |
| #endif |
| |
| bool pushedNewTarget = op == JSOP_NEW; |
| |
| // 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 (!iter.moreFrames() || catchingException) { |
| // Last frame, so PC for call to next frame is set to nullptr. |
| *callPC = nullptr; |
| |
| // If the bailout was a resumeAfter, and the opcode is monitored, |
| // then the bailed out state should be in a position to enter |
| // into the ICTypeMonitor chain for the op. |
| bool enterMonitorChain = false; |
| if (resumeAfter && (CodeSpec[op].format & JOF_TYPESET)) { |
| // Not every monitored op has a monitored fallback stub, e.g. |
| // JSOP_NEWOBJECT, which always returns the same type for a |
| // particular script/pc location. |
| ICEntry& icEntry = baselineScript->icEntryFromPCOffset(pcOff); |
| ICFallbackStub* fallbackStub = icEntry.firstStub()->getChainFallback(); |
| if (fallbackStub->isMonitoredFallback()) |
| enterMonitorChain = true; |
| } |
| |
| uint32_t numCallArgs = isCall ? GET_ARGC(pc) : 0; |
| |
| if (resumeAfter && !enterMonitorChain) |
| pc = GetNextPc(pc); |
| |
| builder.setResumePC(pc); |
| builder.setResumeFramePtr(prevFramePtr); |
| |
| if (enterMonitorChain) { |
| ICEntry& icEntry = baselineScript->icEntryFromPCOffset(pcOff); |
| ICFallbackStub* fallbackStub = icEntry.firstStub()->getChainFallback(); |
| MOZ_ASSERT(fallbackStub->isMonitoredFallback()); |
| JitSpew(JitSpew_BaselineBailouts, " [TYPE-MONITOR CHAIN]"); |
| ICMonitoredFallbackStub* monFallbackStub = fallbackStub->toMonitoredFallbackStub(); |
| ICStub* firstMonStub = monFallbackStub->fallbackMonitorStub()->firstMonitorStub(); |
| |
| // To enter a monitoring chain, we load the top stack value into R0 |
| JitSpew(JitSpew_BaselineBailouts, " Popping top stack value into R0."); |
| builder.popValueInto(PCMappingSlotInfo::SlotInR0); |
| |
| // Need to adjust the frameSize for the frame to match the values popped |
| // into registers. |
| frameSize -= sizeof(Value); |
| blFrame->setFrameSize(frameSize); |
| JitSpew(JitSpew_BaselineBailouts, " Adjusted framesize -= %d: %d", |
| (int) sizeof(Value), (int) frameSize); |
| |
| // If resuming into a JSOP_CALL, baseline keeps the arguments on the |
| // stack and pops them only after returning from the call IC. |
| // Push undefs onto the stack in anticipation of the popping of the |
| // callee, thisv, and actual arguments passed from the caller's frame. |
| if (isCall) { |
| builder.writeValue(UndefinedValue(), "CallOp FillerCallee"); |
| builder.writeValue(UndefinedValue(), "CallOp FillerThis"); |
| for (uint32_t i = 0; i < numCallArgs; i++) |
| builder.writeValue(UndefinedValue(), "CallOp FillerArg"); |
| if (pushedNewTarget) |
| builder.writeValue(UndefinedValue(), "CallOp FillerNewTarget"); |
| |
| frameSize += (numCallArgs + 2 + pushedNewTarget) * sizeof(Value); |
| blFrame->setFrameSize(frameSize); |
| JitSpew(JitSpew_BaselineBailouts, " Adjusted framesize += %d: %d", |
| (int) ((numCallArgs + 2 + pushedNewTarget) * sizeof(Value)), |
| (int) frameSize); |
| } |
| |
| // Set the resume address to the return point from the IC, and set |
| // the monitor stub addr. |
| builder.setResumeAddr(baselineScript->returnAddressForIC(icEntry)); |
| builder.setMonitorStub(firstMonStub); |
| JitSpew(JitSpew_BaselineBailouts, " Set resumeAddr=%p monitorStub=%p", |
| baselineScript->returnAddressForIC(icEntry), firstMonStub); |
| |
| } else { |
| // If needed, initialize BaselineBailoutInfo's valueR0 and/or valueR1 with the |
| // top stack values. |
| // |
| // Note that we use the 'maybe' variant of nativeCodeForPC because |
| // of exception propagation for debug mode. See note below. |
| PCMappingSlotInfo slotInfo; |
| uint8_t* nativeCodeForPC; |
| |
| if (excInfo && excInfo->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 (this pc will be used as the BaselineFrame's |
| // override pc). |
| // |
| // Note that we never resume at this pc, it is set for the sake |
| // of frame iterators giving the correct answer. |
| // |
| // We also set nativeCodeForPC to nullptr as this address |
| // won't be used anywhere. |
| jsbytecode* throwPC = script->offsetToPC(iter.pcOffset()); |
| builder.setResumePC(throwPC); |
| nativeCodeForPC = nullptr; |
| } else { |
| nativeCodeForPC = baselineScript->nativeCodeForPC(script, pc, &slotInfo); |
| MOZ_ASSERT(nativeCodeForPC); |
| } |
| |
| unsigned numUnsynced = slotInfo.numUnsynced(); |
| |
| MOZ_ASSERT(numUnsynced <= 2); |
| PCMappingSlotInfo::SlotLocation loc1, loc2; |
| if (numUnsynced > 0) { |
| loc1 = slotInfo.topSlotLocation(); |
| JitSpew(JitSpew_BaselineBailouts, " Popping top stack value into %d.", |
| (int) loc1); |
| builder.popValueInto(loc1); |
| } |
| if (numUnsynced > 1) { |
| loc2 = slotInfo.nextSlotLocation(); |
| JitSpew(JitSpew_BaselineBailouts, " Popping next stack value into %d.", |
| (int) loc2); |
| MOZ_ASSERT_IF(loc1 != PCMappingSlotInfo::SlotIgnore, loc1 != loc2); |
| builder.popValueInto(loc2); |
| } |
| |
| // Need to adjust the frameSize for the frame to match the values popped |
| // into registers. |
| frameSize -= sizeof(Value) * numUnsynced; |
| blFrame->setFrameSize(frameSize); |
| JitSpew(JitSpew_BaselineBailouts, " Adjusted framesize -= %d: %d", |
| int(sizeof(Value) * numUnsynced), int(frameSize)); |
| |
| // If scopeChain is nullptr, then bailout is occurring during argument check. |
| // In this case, resume into the prologue. |
| uint8_t* opReturnAddr; |
| if (scopeChain == nullptr) { |
| // Global and eval scripts expect the scope chain in R1, so only |
| // resume into the prologue for function scripts. |
| MOZ_ASSERT(fun); |
| MOZ_ASSERT(numUnsynced == 0); |
| opReturnAddr = baselineScript->prologueEntryAddr(); |
| JitSpew(JitSpew_BaselineBailouts, " Resuming into prologue."); |
| |
| } else { |
| opReturnAddr = nativeCodeForPC; |
| } |
| builder.setResumeAddr(opReturnAddr); |
| JitSpew(JitSpew_BaselineBailouts, " Set resumeAddr=%p", opReturnAddr); |
| } |
| |
| if (cx->runtime()->spsProfiler.enabled()) { |
| // Register bailout with profiler. |
| const char* filename = script->filename(); |
| if (filename == nullptr) |
| filename = "<unknown>"; |
| unsigned len = strlen(filename) + 200; |
| char* buf = js_pod_malloc<char>(len); |
| if (buf == nullptr) |
| return false; |
| JS_snprintf(buf, len, "%s %s %s on line %u of %s:%" PRIuSIZE, |
| BailoutKindString(bailoutKind), |
| resumeAfter ? "after" : "at", |
| CodeName[op], |
| PCToLineNumber(script, pc), |
| filename, |
| script->lineno()); |
| cx->runtime()->spsProfiler.markEvent(buf); |
| js_free(buf); |
| } |
| |
| return true; |
| } |
| |
| *callPC = pc; |
| |
| // Write out descriptor of BaselineJS frame. |
| size_t baselineFrameDescr = MakeFrameDescriptor((uint32_t) builder.framePushed(), |
| JitFrame_BaselineJS); |
| if (!builder.writeWord(baselineFrameDescr, "Descriptor")) |
| return false; |
| |
| // Calculate and write out return address. |
| // The icEntry in question MUST have an inlinable fallback stub. |
| ICEntry& icEntry = baselineScript->icEntryFromPCOffset(pcOff); |
| MOZ_ASSERT(IsInlinableFallback(icEntry.firstStub()->getChainFallback())); |
| if (!builder.writePtr(baselineScript->returnAddressForIC(icEntry), "ReturnAddr")) |
| return false; |
| |
| // Build baseline stub frame: |
| // +===============+ |
| // | StubPtr | |
| // +---------------+ |
| // | FramePtr | |
| // +---------------+ |
| // | Padding? | |
| // +---------------+ |
| // | ArgA | |
| // +---------------+ |
| // | ... | |
| // +---------------+ |
| // | Arg0 | |
| // +---------------+ |
| // | ThisV | |
| // +---------------+ |
| // | ActualArgC | |
| // +---------------+ |
| // | CalleeToken | |
| // +---------------+ |
| // | Descr(BLStub) | |
| // +---------------+ |
| // | ReturnAddr | |
| // +===============+ |
| |
| JitSpew(JitSpew_BaselineBailouts, " [BASELINE-STUB FRAME]"); |
| |
| size_t startOfBaselineStubFrame = builder.framePushed(); |
| |
| // Write stub pointer. |
| MOZ_ASSERT(IsInlinableFallback(icEntry.fallbackStub())); |
| if (!builder.writePtr(icEntry.fallbackStub(), "StubPtr")) |
| return false; |
| |
| // Write previous frame pointer (saved earlier). |
| if (!builder.writePtr(prevFramePtr, "PrevFramePtr")) |
| return false; |
| prevFramePtr = builder.virtualPointerAtStackOffset(0); |
| |
| // Write out actual arguments (and thisv), copied from unpacked stack of BaselineJS frame. |
| // Arguments are reversed on the BaselineJS frame's stack values. |
| MOZ_ASSERT(IsIonInlinablePC(pc)); |
| unsigned actualArgc; |
| Value callee; |
| if (needToSaveArgs) { |
| // For FUNAPPLY or an accessor, the arguments are not on the stack anymore, |
| // but they are copied in a vector and are written here. |
| if (op == JSOP_FUNAPPLY) |
| actualArgc = blFrame->numActualArgs(); |
| else |
| actualArgc = IsSetPropPC(pc); |
| callee = savedCallerArgs[0]; |
| |
| // Align the stack based on the number of arguments. |
| size_t afterFrameSize = (actualArgc + 1) * sizeof(Value) + JitFrameLayout::Size(); |
| if (!builder.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 (!builder.writeValue(savedCallerArgs[arg], "ArgVal")) |
| return false; |
| } |
| } else { |
| actualArgc = GET_ARGC(pc); |
| if (op == JSOP_FUNCALL) { |
| MOZ_ASSERT(actualArgc > 0); |
| actualArgc--; |
| } |
| |
| // Align the stack based on the number of arguments. |
| size_t afterFrameSize = (actualArgc + 1 + pushedNewTarget) * sizeof(Value) + |
| JitFrameLayout::Size(); |
| if (!builder.maybeWritePadding(JitStackAlignment, afterFrameSize, "Padding")) |
| return false; |
| |
| // Copy the arguments and |this| from the BaselineFrame, in reverse order. |
| size_t valueSlot = blFrame->numValueSlots() - 1; |
| size_t calleeSlot = valueSlot - actualArgc - 1 - pushedNewTarget; |
| |
| for (size_t i = valueSlot; i > calleeSlot; i--) { |
| if (!builder.writeValue(*blFrame->valueSlot(i), "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 = builder.framePushed(); |
| |
| // Calculate frame size for descriptor. |
| size_t baselineStubFrameSize = builder.framePushed() - startOfBaselineStubFrame; |
| size_t baselineStubFrameDescr = MakeFrameDescriptor((uint32_t) baselineStubFrameSize, |
| JitFrame_BaselineStub); |
| |
| // Push actual argc |
| if (!builder.writeWord(actualArgc, "ActualArgc")) |
| return false; |
| |
| // Push callee token (must be a JS Function) |
| JitSpew(JitSpew_BaselineBailouts, " Callee = %016llx", callee.asRawBits()); |
| |
| JSFunction* calleeFun = &callee.toObject().as<JSFunction>(); |
| if (!builder.writePtr(CalleeToToken(calleeFun, JSOp(*pc) == JSOP_NEW), "CalleeToken")) |
| return false; |
| nextCallee.set(calleeFun); |
| |
| // Push BaselineStub frame descriptor |
| if (!builder.writeWord(baselineStubFrameDescr, "Descriptor")) |
| return false; |
| |
| // Push return address into ICCall_Scripted stub, immediately after the call. |
| void* baselineCallReturnAddr = GetStubReturnAddress(cx, pc); |
| MOZ_ASSERT(baselineCallReturnAddr); |
| if (!builder.writePtr(baselineCallReturnAddr, "ReturnAddr")) |
| return false; |
| MOZ_ASSERT(builder.framePushed() % JitStackAlignment == 0); |
| |
| // If actualArgc >= fun->nargs, then we are done. Otherwise, we need to push on |
| // a reconstructed rectifier frame. |
| if (actualArgc >= calleeFun->nargs()) |
| return true; |
| |
| // Push a reconstructed rectifier frame. |
| // +===============+ |
| // | Padding? | |
| // +---------------+ |
| // | UndefinedU | |
| // +---------------+ |
| // | ... | |
| // +---------------+ |
| // | Undefined0 | |
| // +---------------+ |
| // | ArgA | |
| // +---------------+ |
| // | ... | |
| // +---------------+ |
| // | Arg0 | |
| // +---------------+ |
| // | ThisV | |
| // +---------------+ |
| // | ActualArgC | |
| // +---------------+ |
| // | CalleeToken | |
| // +---------------+ |
| // | Descr(Rect) | |
| // +---------------+ |
| // | ReturnAddr | |
| // +===============+ |
| |
| JitSpew(JitSpew_BaselineBailouts, " [RECTIFIER FRAME]"); |
| |
| size_t startOfRectifierFrame = builder.framePushed(); |
| |
| // On x86-only, the frame pointer is saved again in the rectifier frame. |
| #if defined(JS_CODEGEN_X86) |
| if (!builder.writePtr(prevFramePtr, "PrevFramePtr-X86Only")) |
| return false; |
| // Follow the same logic as in JitRuntime::generateArgumentsRectifier. |
| prevFramePtr = builder.virtualPointerAtStackOffset(0); |
| if (!builder.writePtr(prevFramePtr, "Padding-X86Only")) |
| return false; |
| #endif |
| |
| // Align the stack based on the number of arguments. |
| size_t afterFrameSize = (calleeFun->nargs() + 1 + pushedNewTarget) * sizeof(Value) + |
| RectifierFrameLayout::Size(); |
| if (!builder.maybeWritePadding(JitStackAlignment, afterFrameSize, "Padding")) |
| return false; |
| |
| // Copy new.target, if necessary. |
| if (pushedNewTarget) { |
| size_t newTargetOffset = (builder.framePushed() - endOfBaselineStubArgs) + |
| (actualArgc + 1) * sizeof(Value); |
| builder.writeValue(*builder.valuePointerAtStackOffset(newTargetOffset), "CopiedNewTarget"); |
| } |
| |
| // Push undefined for missing arguments. |
| for (unsigned i = 0; i < (calleeFun->nargs() - actualArgc); i++) { |
| if (!builder.writeValue(UndefinedValue(), "FillerVal")) |
| return false; |
| } |
| |
| // Copy arguments + thisv from BaselineStub frame. |
| if (!builder.subtract((actualArgc + 1) * sizeof(Value), "CopiedArgs")) |
| return false; |
| BufferPointer<uint8_t> stubArgsEnd = |
| builder.pointerAtStackOffset<uint8_t>(builder.framePushed() - endOfBaselineStubArgs); |
| JitSpew(JitSpew_BaselineBailouts, " MemCpy from %p", stubArgsEnd.get()); |
| memcpy(builder.pointerAtStackOffset<uint8_t>(0).get(), stubArgsEnd.get(), |
| (actualArgc + 1) * sizeof(Value)); |
| |
| // Calculate frame size for descriptor. |
| size_t rectifierFrameSize = builder.framePushed() - startOfRectifierFrame; |
| size_t rectifierFrameDescr = MakeFrameDescriptor((uint32_t) rectifierFrameSize, |
| JitFrame_Rectifier); |
| |
| // Push actualArgc |
| if (!builder.writeWord(actualArgc, "ActualArgc")) |
| return false; |
| |
| // Push calleeToken again. |
| if (!builder.writePtr(CalleeToToken(calleeFun, JSOp(*pc) == JSOP_NEW), "CalleeToken")) |
| return false; |
| |
| // Push rectifier frame descriptor |
| if (!builder.writeWord(rectifierFrameDescr, "Descriptor")) |
| return false; |
| |
| // Push return address into the ArgumentsRectifier code, immediately after the ioncode |
| // call. |
| void* rectReturnAddr = cx->runtime()->jitRuntime()->getArgumentsRectifierReturnAddr(); |
| MOZ_ASSERT(rectReturnAddr); |
| if (!builder.writePtr(rectReturnAddr, "ReturnAddr")) |
| return false; |
| MOZ_ASSERT(builder.framePushed() % JitStackAlignment == 0); |
| |
| return true; |
| } |
| |
| uint32_t |
| jit::BailoutIonToBaseline(JSContext* cx, JitActivation* activation, JitFrameIterator& iter, |
| bool invalidate, BaselineBailoutInfo** bailoutInfo, |
| const ExceptionBailoutInfo* excInfo) |
| { |
| MOZ_ASSERT(bailoutInfo != nullptr); |
| MOZ_ASSERT(*bailoutInfo == nullptr); |
| |
| TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); |
| TraceLogStopEvent(logger, TraceLogger_IonMonkey); |
| TraceLogStartEvent(logger, TraceLogger_Baseline); |
| |
| // 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()); |
| }); |
| |
| // The caller of the top frame must be one of the following: |
| // IonJS - Ion calling into Ion. |
| // BaselineStub - Baseline calling into Ion. |
| // Entry - Interpreter or other calling into Ion. |
| // Rectifier - Arguments rectifier calling into Ion. |
| MOZ_ASSERT(iter.isBailoutJS()); |
| #if defined(DEBUG) || defined(JS_JITSPEW) |
| FrameType prevFrameType = iter.prevType(); |
| MOZ_ASSERT(prevFrameType == JitFrame_IonJS || |
| prevFrameType == JitFrame_BaselineStub || |
| prevFrameType == JitFrame_Entry || |
| prevFrameType == JitFrame_Rectifier || |
| prevFrameType == JitFrame_IonAccessorIC); |
| #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 (IonScript=%p) (FrameType=%d)", |
| iter.script()->filename(), iter.script()->lineno(), (void*) iter.ionScript(), |
| (int) prevFrameType); |
| |
| bool catchingException; |
| bool propagatingExceptionForDebugMode; |
| if (excInfo) { |
| catchingException = excInfo->catchingException(); |
| propagatingExceptionForDebugMode = excInfo->propagatingIonExceptionForDebugMode(); |
| |
| if (catchingException) |
| JitSpew(JitSpew_BaselineBailouts, "Resuming in catch or finally block"); |
| |
| if (propagatingExceptionForDebugMode) |
| JitSpew(JitSpew_BaselineBailouts, "Resuming in-place for debug mode"); |
| } else { |
| catchingException = false; |
| propagatingExceptionForDebugMode = false; |
| } |
| |
| JitSpew(JitSpew_BaselineBailouts, " Reading from snapshot offset %u size %u", |
| iter.snapshotOffset(), iter.ionScript()->snapshotsListSize()); |
| |
| if (!excInfo) |
| iter.ionScript()->incNumBailouts(); |
| iter.script()->updateBaselineOrIonRaw(cx); |
| |
| // Allocate buffer to hold stack replacement data. |
| BaselineStackBuilder builder(iter, 1024); |
| if (!builder.init()) { |
| ReportOutOfMemory(cx); |
| return BAILOUT_RETURN_FATAL_ERROR; |
| } |
| JitSpew(JitSpew_BaselineBailouts, " Incoming frame ptr = %p", builder.startFrame()); |
| |
| SnapshotIteratorForBailout snapIter(activation, iter); |
| if (!snapIter.init(cx)) |
| return BAILOUT_RETURN_FATAL_ERROR; |
| |
| #ifdef TRACK_SNAPSHOTS |
| snapIter.spewBailingFrom(); |
| #endif |
| |
| RootedFunction callee(cx, iter.maybeCallee()); |
| RootedScript scr(cx, iter.script()); |
| if (callee) { |
| JitSpew(JitSpew_BaselineBailouts, " Callee function (%s:%u)", |
| scr->filename(), scr->lineno()); |
| } else { |
| JitSpew(JitSpew_BaselineBailouts, " No callee!"); |
| } |
| |
| if (iter.isConstructing()) |
| JitSpew(JitSpew_BaselineBailouts, " Constructing!"); |
| else |
| JitSpew(JitSpew_BaselineBailouts, " Not constructing!"); |
| |
| JitSpew(JitSpew_BaselineBailouts, " Restoring frames:"); |
| size_t frameNo = 0; |
| |
| // Reconstruct baseline frames using the builder. |
| RootedScript caller(cx); |
| jsbytecode* callerPC = nullptr; |
| RootedFunction fun(cx, callee); |
| AutoValueVector startFrameFormals(cx); |
| |
| gc::AutoSuppressGC suppress(cx); |
| |
| while (true) { |
| // Skip recover instructions as they are already recovered by |initInstructionResults|. |
| snapIter.settleOnFrame(); |
| |
| if (frameNo > 0) { |
| // TraceLogger doesn't create entries for inlined frames. But we |
| // see them in Baseline. Here we create the start events of those |
| // entries. So they correspond to what we will see in Baseline. |
| TraceLoggerEvent scriptEvent(logger, TraceLogger_Scripts, scr); |
| TraceLogStartEvent(logger, scriptEvent); |
| TraceLogStartEvent(logger, TraceLogger_Baseline); |
| } |
| |
| JitSpew(JitSpew_BaselineBailouts, " FrameNo %d", frameNo); |
| |
| // If we are bailing out to a catch or finally block in this frame, |
| // pass excInfo to InitFromBailout and don't unpack any other frames. |
| bool handleException = (catchingException && excInfo->frameNo() == frameNo); |
| |
| // We also need to pass excInfo if we're bailing out in place for |
| // debug mode. |
| bool passExcInfo = handleException || propagatingExceptionForDebugMode; |
| |
| jsbytecode* callPC = nullptr; |
| RootedFunction nextCallee(cx, nullptr); |
| if (!InitFromBailout(cx, caller, callerPC, fun, scr, iter.ionScript(), |
| snapIter, invalidate, builder, startFrameFormals, |
| &nextCallee, &callPC, passExcInfo ? excInfo : nullptr)) |
| { |
| return BAILOUT_RETURN_FATAL_ERROR; |
| } |
| |
| if (!snapIter.moreFrames()) { |
| MOZ_ASSERT(!callPC); |
| break; |
| } |
| |
| if (handleException) |
| break; |
| |
| MOZ_ASSERT(nextCallee); |
| MOZ_ASSERT(callPC); |
| caller = scr; |
| callerPC = callPC; |
| fun = nextCallee; |
| scr = fun->existingScriptForInlinedFunction(); |
| |
| frameNo++; |
| |
| snapIter.nextInstruction(); |
| } |
| JitSpew(JitSpew_BaselineBailouts, " Done restoring frames"); |
| |
| BailoutKind bailoutKind = snapIter.bailoutKind(); |
| |
| if (!startFrameFormals.empty()) { |
| // Set the first frame's formals, see the comment in InitFromBailout. |
| Value* argv = builder.startFrame()->argv() + 1; // +1 to skip |this|. |
| mozilla::PodCopy(argv, startFrameFormals.begin(), startFrameFormals.length()); |
| } |
| |
| // Do stack check. |
| bool overRecursed = false; |
| BaselineBailoutInfo *info = builder.info(); |
| uint8_t* newsp = info->incomingStack - (info->copyStackTop - info->copyStackBottom); |
| #ifdef JS_SIMULATOR |
| if (Simulator::Current()->overRecursed(uintptr_t(newsp))) |
| overRecursed = true; |
| #else |
| JS_CHECK_RECURSION_WITH_SP_DONT_REPORT(cx, newsp, overRecursed = true); |
| #endif |
| if (overRecursed) { |
| JitSpew(JitSpew_BaselineBailouts, " Overrecursion check failed!"); |
| return BAILOUT_RETURN_OVERRECURSED; |
| } |
| |
| // Take the reconstructed baseline stack so it doesn't get freed when builder destructs. |
| info = builder.takeBuffer(); |
| info->numFrames = frameNo + 1; |
| info->bailoutKind = bailoutKind; |
| *bailoutInfo = info; |
| guardRemoveRematerializedFramesFromDebugger.release(); |
| return BAILOUT_RETURN_OK; |
| } |
| |
| static bool |
| 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 true; |
| } |
| |
| MOZ_ASSERT(!outerScript->ionScript()->invalidated()); |
| |
| JitSpew(JitSpew_BaselineBailouts, "Invalidating due to %s", reason); |
| return Invalidate(cx, outerScript); |
| } |
| |
| static bool |
| HandleBoundsCheckFailure(JSContext* cx, HandleScript outerScript, HandleScript innerScript) |
| { |
| JitSpew(JitSpew_IonBailouts, "Bounds check failure %s:%d, inlined into %s:%d", |
| innerScript->filename(), innerScript->lineno(), |
| outerScript->filename(), outerScript->lineno()); |
| |
| if (!innerScript->failedBoundsCheck()) |
| innerScript->setFailedBoundsCheck(); |
| |
| if (!InvalidateAfterBailout(cx, outerScript, "bounds check failure")) |
| return false; |
| |
| if (innerScript->hasIonScript() && !Invalidate(cx, innerScript)) |
| return false; |
| |
| return true; |
| } |
| |
| static bool |
| HandleShapeGuardFailure(JSContext* cx, HandleScript outerScript, HandleScript innerScript) |
| { |
| JitSpew(JitSpew_IonBailouts, "Shape guard failure %s:%d, inlined into %s:%d", |
| innerScript->filename(), innerScript->lineno(), |
| outerScript->filename(), outerScript->lineno()); |
| |
| // TODO: Currently this mimic's Ion's handling of this case. Investigate setting |
| // the flag on innerScript as opposed to outerScript, and maybe invalidating both |
| // inner and outer scripts, instead of just the outer one. |
| outerScript->setFailedShapeGuard(); |
| |
| return InvalidateAfterBailout(cx, outerScript, "shape guard failure"); |
| } |
| |
| static bool |
| HandleBaselineInfoBailout(JSContext* cx, HandleScript outerScript, HandleScript innerScript) |
| { |
| JitSpew(JitSpew_IonBailouts, "Baseline info failure %s:%d, inlined into %s:%d", |
| innerScript->filename(), innerScript->lineno(), |
| outerScript->filename(), outerScript->lineno()); |
| |
| return InvalidateAfterBailout(cx, outerScript, "invalid baseline info"); |
| } |
| |
| static bool |
| HandleLexicalCheckFailure(JSContext* cx, HandleScript outerScript, HandleScript innerScript) |
| { |
| JitSpew(JitSpew_IonBailouts, "Lexical check failure %s:%d, inlined into %s:%d", |
| innerScript->filename(), innerScript->lineno(), |
| outerScript->filename(), outerScript->lineno()); |
| |
| if (!innerScript->failedLexicalCheck()) |
| innerScript->setFailedLexicalCheck(); |
| |
| if (!InvalidateAfterBailout(cx, outerScript, "lexical check failure")) |
| return false; |
| |
| if (innerScript->hasIonScript() && !Invalidate(cx, innerScript)) |
| return false; |
| |
| return true; |
| } |
| |
| 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->setScopeChain(rematFrame->scopeChain()); |
| |
| if (frame->isNonEvalFunctionFrame()) |
| 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]; |
| |
| if (rematFrame->hasCachedSavedFrame()) |
| frame->setHasCachedSavedFrame(); |
| |
| JitSpew(JitSpew_BaselineBailouts, |
| " Copied from rematerialized frame at (%p,%u)", |
| 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 Debugger::handleIonBailout(cx, rematFrame, frame); |
| } |
| |
| return true; |
| } |
| |
| uint32_t |
| jit::FinishBailoutToBaseline(BaselineBailoutInfo* bailoutInfo) |
| { |
| // The caller pushes R0 and R1 on the stack without rooting them. |
| // Since GC here is very unlikely just suppress it. |
| JSContext* cx = GetJSContextFromJitCode(); |
| js::gc::AutoSuppressGC suppressGC(cx); |
| |
| JitSpew(JitSpew_BaselineBailouts, " Done restoring frames"); |
| |
| // The current native code pc may not have a corresponding ICEntry, so we |
| // store the bytecode pc in the frame for frame iterators. This pc is |
| // cleared at the end of this function. If we return false, we don't clear |
| // it: the exception handler also needs it and will clear it for us. |
| BaselineFrame* topFrame = GetTopBaselineFrame(cx); |
| topFrame->setOverridePc(bailoutInfo->resumePC); |
| |
| uint32_t numFrames = bailoutInfo->numFrames; |
| MOZ_ASSERT(numFrames > 0); |
| BailoutKind bailoutKind = bailoutInfo->bailoutKind; |
| |
| // Free the bailout buffer. |
| js_free(bailoutInfo); |
| bailoutInfo = nullptr; |
| |
| // Ensure the frame has a call object if it needs one. If the scope chain |
| // is nullptr, we will enter baseline code at the prologue so no need to do |
| // anything in that case. |
| if (topFrame->scopeChain() && !EnsureHasScopeObjects(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()); |
| JitFrameIterator iter(cx); |
| 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())) |
| cx->runtime()->jitActivation->setLastProfilingFrame(iter.prevFp()); |
| |
| 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 scope chain set yet, then it's resuming |
| // into the the prologue before the scope chain is initialized. Any |
| // necessary args object will also be initialized there. |
| if (frame->scopeChain() && 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(); |
| } |
| |
| 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->runtime()->activation()->asJit(); |
| if (act->hasRematerializedFrame(outerFp)) { |
| JitFrameIterator iter(cx); |
| size_t inlineDepth = numFrames; |
| while (inlineDepth > 0) { |
| if (iter.isBaselineJS() && |
| !CopyFromRematerializedFrame(cx, act, outerFp, --inlineDepth, |
| iter.baselineFrame())) |
| { |
| return false; |
| } |
| ++iter; |
| } |
| |
| // After copying from all the rematerialized frames, remove them from |
| // the table to keep the table up to date. |
| act->removeRematerializedFrame(outerFp); |
| } |
| |
| JitSpew(JitSpew_BaselineBailouts, |
| " Restored outerScript=(%s:%u,%u) innerScript=(%s:%u,%u) (bailoutKind=%u)", |
| outerScript->filename(), outerScript->lineno(), outerScript->getWarmUpCount(), |
| innerScript->filename(), innerScript->lineno(), innerScript->getWarmUpCount(), |
| (unsigned) bailoutKind); |
| |
| switch (bailoutKind) { |
| // Normal bailouts. |
| case Bailout_Inevitable: |
| case Bailout_DuringVMCall: |
| case Bailout_NonJSFunctionCallee: |
| case Bailout_DynamicNameNotFound: |
| case Bailout_StringArgumentsEval: |
| case Bailout_Overflow: |
| case Bailout_Round: |
| case Bailout_NonPrimitiveInput: |
| case Bailout_PrecisionLoss: |
| case Bailout_TypeBarrierO: |
| case Bailout_TypeBarrierV: |
| case Bailout_MonitorTypes: |
| case Bailout_Hole: |
| case Bailout_NegativeIndex: |
| case Bailout_NonInt32Input: |
| case Bailout_NonNumericInput: |
| case Bailout_NonBooleanInput: |
| case Bailout_NonObjectInput: |
| case Bailout_NonStringInput: |
| case Bailout_NonSymbolInput: |
| case Bailout_NonSimdInt32x4Input: |
| case Bailout_NonSimdFloat32x4Input: |
| case Bailout_NonSharedTypedArrayInput: |
| case Bailout_InitialState: |
| case Bailout_Debugger: |
| case Bailout_UninitializedThis: |
| case Bailout_BadDerivedConstructorReturn: |
| // Do nothing. |
| break; |
| |
| case Bailout_FirstExecution: |
| // Do not return directly, as this was not frequent in the first place, |
| // thus rely on the check for frequent bailouts to recompile the current |
| // script. |
| break; |
| |
| // Invalid assumption based on baseline code. |
| case Bailout_OverflowInvalidate: |
| case Bailout_NonStringInputInvalidate: |
| case Bailout_DoubleOutput: |
| case Bailout_ObjectIdentityOrTypeGuard: |
| if (!HandleBaselineInfoBailout(cx, outerScript, innerScript)) |
| return false; |
| break; |
| |
| case Bailout_ArgumentCheck: |
| // Do nothing, bailout will resume before the argument monitor ICs. |
| break; |
| case Bailout_BoundsCheck: |
| case Bailout_Neutered: |
| if (!HandleBoundsCheckFailure(cx, outerScript, innerScript)) |
| return false; |
| break; |
| case Bailout_ShapeGuard: |
| if (!HandleShapeGuardFailure(cx, outerScript, innerScript)) |
| return false; |
| break; |
| case Bailout_UninitializedLexical: |
| if (!HandleLexicalCheckFailure(cx, outerScript, innerScript)) |
| return false; |
| break; |
| case Bailout_IonExceptionDebugMode: |
| // Return false to resume in HandleException with reconstructed |
| // baseline frame. |
| return false; |
| default: |
| MOZ_CRASH("Unknown bailout kind!"); |
| } |
| |
| if (!CheckFrequentBailouts(cx, outerScript, bailoutKind)) |
| return false; |
| |
| // We're returning to JIT code, so we should clear the override pc. |
| topFrame->clearOverridePc(); |
| return true; |
| } |