michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jsprf.h" michael@0: #include "jit/arm/Simulator-arm.h" michael@0: #include "jit/BaselineIC.h" michael@0: #include "jit/BaselineJIT.h" michael@0: #include "jit/CompileInfo.h" michael@0: #include "jit/IonSpewer.h" michael@0: #include "jit/Recover.h" michael@0: #include "jit/RematerializedFrame.h" michael@0: michael@0: #include "vm/ArgumentsObject.h" michael@0: #include "vm/Debugger.h" michael@0: #include "vm/TraceLogging.h" michael@0: michael@0: #include "jsscriptinlines.h" michael@0: michael@0: #include "jit/IonFrames-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::jit; michael@0: michael@0: // BaselineStackBuilder may reallocate its buffer if the current one is too michael@0: // small. To avoid dangling pointers, BufferPointer represents a pointer into michael@0: // this buffer as a pointer to the header and a fixed offset. michael@0: template michael@0: class BufferPointer michael@0: { michael@0: BaselineBailoutInfo **header_; michael@0: size_t offset_; michael@0: bool heap_; michael@0: michael@0: public: michael@0: BufferPointer(BaselineBailoutInfo **header, size_t offset, bool heap) michael@0: : header_(header), offset_(offset), heap_(heap) michael@0: { } michael@0: michael@0: T *get() const { michael@0: BaselineBailoutInfo *header = *header_; michael@0: if (!heap_) michael@0: return (T*)(header->incomingStack + offset_); michael@0: michael@0: uint8_t *p = header->copyStackTop - offset_; michael@0: JS_ASSERT(p >= header->copyStackBottom && p < header->copyStackTop); michael@0: return (T*)p; michael@0: } michael@0: michael@0: T &operator*() const { return *get(); } michael@0: T *operator->() const { return get(); } michael@0: }; michael@0: michael@0: /** michael@0: * BaselineStackBuilder helps abstract the process of rebuilding the C stack on the heap. michael@0: * It takes a bailout iterator and keeps track of the point on the C stack from which michael@0: * the reconstructed frames will be written. michael@0: * michael@0: * It exposes methods to write data into the heap memory storing the reconstructed michael@0: * stack. It also exposes method to easily calculate addresses. This includes both the michael@0: * virtual address that a particular value will be at when it's eventually copied onto michael@0: * the stack, as well as the current actual address of that value (whether on the heap michael@0: * allocated portion being constructed or the existing stack). michael@0: * michael@0: * The abstraction handles transparent re-allocation of the heap memory when it michael@0: * needs to be enlarged to accomodate new data. Similarly to the C stack, the michael@0: * data that's written to the reconstructed stack grows from high to low in memory. michael@0: * michael@0: * The lowest region of the allocated memory contains a BaselineBailoutInfo structure that michael@0: * points to the start and end of the written data. michael@0: */ michael@0: struct BaselineStackBuilder michael@0: { michael@0: IonBailoutIterator &iter_; michael@0: IonJSFrameLayout *frame_; michael@0: michael@0: static size_t HeaderSize() { michael@0: return AlignBytes(sizeof(BaselineBailoutInfo), sizeof(void *)); michael@0: }; michael@0: size_t bufferTotal_; michael@0: size_t bufferAvail_; michael@0: size_t bufferUsed_; michael@0: uint8_t *buffer_; michael@0: BaselineBailoutInfo *header_; michael@0: michael@0: size_t framePushed_; michael@0: michael@0: BaselineStackBuilder(IonBailoutIterator &iter, size_t initialSize) michael@0: : iter_(iter), michael@0: frame_(static_cast(iter.current())), michael@0: bufferTotal_(initialSize), michael@0: bufferAvail_(0), michael@0: bufferUsed_(0), michael@0: buffer_(nullptr), michael@0: header_(nullptr), michael@0: framePushed_(0) michael@0: { michael@0: JS_ASSERT(bufferTotal_ >= HeaderSize()); michael@0: } michael@0: michael@0: ~BaselineStackBuilder() { michael@0: js_free(buffer_); michael@0: } michael@0: michael@0: bool init() { michael@0: JS_ASSERT(!buffer_); michael@0: JS_ASSERT(bufferUsed_ == 0); michael@0: buffer_ = reinterpret_cast(js_calloc(bufferTotal_)); michael@0: if (!buffer_) michael@0: return false; michael@0: bufferAvail_ = bufferTotal_ - HeaderSize(); michael@0: bufferUsed_ = 0; michael@0: michael@0: header_ = reinterpret_cast(buffer_); michael@0: header_->incomingStack = reinterpret_cast(frame_); michael@0: header_->copyStackTop = buffer_ + bufferTotal_; michael@0: header_->copyStackBottom = header_->copyStackTop; michael@0: header_->setR0 = 0; michael@0: header_->valueR0 = UndefinedValue(); michael@0: header_->setR1 = 0; michael@0: header_->valueR1 = UndefinedValue(); michael@0: header_->resumeFramePtr = nullptr; michael@0: header_->resumeAddr = nullptr; michael@0: header_->monitorStub = nullptr; michael@0: header_->numFrames = 0; michael@0: return true; michael@0: } michael@0: michael@0: bool enlarge() { michael@0: JS_ASSERT(buffer_ != nullptr); michael@0: if (bufferTotal_ & mozilla::tl::MulOverflowMask<2>::value) michael@0: return false; michael@0: size_t newSize = bufferTotal_ * 2; michael@0: uint8_t *newBuffer = reinterpret_cast(js_calloc(newSize)); michael@0: if (!newBuffer) michael@0: return false; michael@0: memcpy((newBuffer + newSize) - bufferUsed_, header_->copyStackBottom, bufferUsed_); michael@0: memcpy(newBuffer, header_, sizeof(BaselineBailoutInfo)); michael@0: js_free(buffer_); michael@0: buffer_ = newBuffer; michael@0: bufferTotal_ = newSize; michael@0: bufferAvail_ = newSize - (HeaderSize() + bufferUsed_); michael@0: michael@0: header_ = reinterpret_cast(buffer_); michael@0: header_->copyStackTop = buffer_ + bufferTotal_; michael@0: header_->copyStackBottom = header_->copyStackTop - bufferUsed_; michael@0: return true; michael@0: } michael@0: michael@0: BaselineBailoutInfo *info() { michael@0: JS_ASSERT(header_ == reinterpret_cast(buffer_)); michael@0: return header_; michael@0: } michael@0: michael@0: BaselineBailoutInfo *takeBuffer() { michael@0: JS_ASSERT(header_ == reinterpret_cast(buffer_)); michael@0: buffer_ = nullptr; michael@0: return header_; michael@0: } michael@0: michael@0: void resetFramePushed() { michael@0: framePushed_ = 0; michael@0: } michael@0: michael@0: size_t framePushed() const { michael@0: return framePushed_; michael@0: } michael@0: michael@0: bool subtract(size_t size, const char *info = nullptr) { michael@0: // enlarge the buffer if need be. michael@0: while (size > bufferAvail_) { michael@0: if (!enlarge()) michael@0: return false; michael@0: } michael@0: michael@0: // write out element. michael@0: header_->copyStackBottom -= size; michael@0: bufferAvail_ -= size; michael@0: bufferUsed_ += size; michael@0: framePushed_ += size; michael@0: if (info) { michael@0: IonSpew(IonSpew_BaselineBailouts, michael@0: " SUB_%03d %p/%p %-15s", michael@0: (int) size, header_->copyStackBottom, virtualPointerAtStackOffset(0), info); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: bool write(const T &t) { michael@0: if (!subtract(sizeof(T))) michael@0: return false; michael@0: memcpy(header_->copyStackBottom, &t, sizeof(T)); michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: bool writePtr(T *t, const char *info) { michael@0: if (!write(t)) michael@0: return false; michael@0: if (info) michael@0: IonSpew(IonSpew_BaselineBailouts, michael@0: " WRITE_PTR %p/%p %-15s %p", michael@0: header_->copyStackBottom, virtualPointerAtStackOffset(0), info, t); michael@0: return true; michael@0: } michael@0: michael@0: bool writeWord(size_t w, const char *info) { michael@0: if (!write(w)) michael@0: return false; michael@0: if (info) { michael@0: if (sizeof(size_t) == 4) { michael@0: IonSpew(IonSpew_BaselineBailouts, michael@0: " WRITE_WRD %p/%p %-15s %08x", michael@0: header_->copyStackBottom, virtualPointerAtStackOffset(0), info, w); michael@0: } else { michael@0: IonSpew(IonSpew_BaselineBailouts, michael@0: " WRITE_WRD %p/%p %-15s %016llx", michael@0: header_->copyStackBottom, virtualPointerAtStackOffset(0), info, w); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool writeValue(Value val, const char *info) { michael@0: if (!write(val)) michael@0: return false; michael@0: if (info) { michael@0: IonSpew(IonSpew_BaselineBailouts, michael@0: " WRITE_VAL %p/%p %-15s %016llx", michael@0: header_->copyStackBottom, virtualPointerAtStackOffset(0), info, michael@0: *((uint64_t *) &val)); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: Value popValue() { michael@0: JS_ASSERT(bufferUsed_ >= sizeof(Value)); michael@0: JS_ASSERT(framePushed_ >= sizeof(Value)); michael@0: bufferAvail_ += sizeof(Value); michael@0: bufferUsed_ -= sizeof(Value); michael@0: framePushed_ -= sizeof(Value); michael@0: Value result = *((Value *) header_->copyStackBottom); michael@0: header_->copyStackBottom += sizeof(Value); michael@0: return result; michael@0: } michael@0: michael@0: void popValueInto(PCMappingSlotInfo::SlotLocation loc) { michael@0: JS_ASSERT(PCMappingSlotInfo::ValidSlotLocation(loc)); michael@0: switch(loc) { michael@0: case PCMappingSlotInfo::SlotInR0: michael@0: header_->setR0 = 1; michael@0: header_->valueR0 = popValue(); michael@0: break; michael@0: case PCMappingSlotInfo::SlotInR1: michael@0: header_->setR1 = 1; michael@0: header_->valueR1 = popValue(); michael@0: break; michael@0: default: michael@0: JS_ASSERT(loc == PCMappingSlotInfo::SlotIgnore); michael@0: popValue(); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void setResumeFramePtr(void *resumeFramePtr) { michael@0: header_->resumeFramePtr = resumeFramePtr; michael@0: } michael@0: michael@0: void setResumeAddr(void *resumeAddr) { michael@0: header_->resumeAddr = resumeAddr; michael@0: } michael@0: michael@0: void setMonitorStub(ICStub *stub) { michael@0: header_->monitorStub = stub; michael@0: } michael@0: michael@0: template michael@0: BufferPointer pointerAtStackOffset(size_t offset) { michael@0: if (offset < bufferUsed_) { michael@0: // Calculate offset from copyStackTop. michael@0: offset = header_->copyStackTop - (header_->copyStackBottom + offset); michael@0: return BufferPointer(&header_, offset, /* heap = */ true); michael@0: } michael@0: michael@0: return BufferPointer(&header_, offset - bufferUsed_, /* heap = */ false); michael@0: } michael@0: michael@0: BufferPointer valuePointerAtStackOffset(size_t offset) { michael@0: return pointerAtStackOffset(offset); michael@0: } michael@0: michael@0: inline uint8_t *virtualPointerAtStackOffset(size_t offset) { michael@0: if (offset < bufferUsed_) michael@0: return reinterpret_cast(frame_) - (bufferUsed_ - offset); michael@0: return reinterpret_cast(frame_) + (offset - bufferUsed_); michael@0: } michael@0: michael@0: inline IonJSFrameLayout *startFrame() { michael@0: return frame_; michael@0: } michael@0: michael@0: BufferPointer topFrameAddress() { michael@0: return pointerAtStackOffset(0); michael@0: } michael@0: michael@0: // michael@0: // This method should only be called when the builder is in a state where it is michael@0: // starting to construct the stack frame for the next callee. This means that michael@0: // the lowest value on the constructed stack is the return address for the previous michael@0: // caller frame. michael@0: // michael@0: // This method is used to compute the value of the frame pointer (e.g. ebp on x86) michael@0: // that would have been saved by the baseline jitcode when it was entered. In some michael@0: // cases, this value can be bogus since we can ensure that the caller would have saved michael@0: // it anyway. michael@0: // michael@0: void *calculatePrevFramePtr() { michael@0: // Get the incoming frame. michael@0: BufferPointer topFrame = topFrameAddress(); michael@0: FrameType type = topFrame->prevType(); michael@0: michael@0: // For IonJS and Entry frames, the "saved" frame pointer in the baseline michael@0: // frame is meaningless, since Ion saves all registers before calling other ion michael@0: // frames, and the entry frame saves all registers too. michael@0: if (type == JitFrame_IonJS || type == JitFrame_Entry) michael@0: return nullptr; michael@0: michael@0: // BaselineStub - Baseline calling into Ion. michael@0: // PrevFramePtr needs to point to the BaselineStubFrame's saved frame pointer. michael@0: // STACK_START_ADDR + IonJSFrameLayout::Size() + PREV_FRAME_SIZE michael@0: // - IonBaselineStubFrameLayout::reverseOffsetOfSavedFramePtr() michael@0: if (type == JitFrame_BaselineStub) { michael@0: size_t offset = IonJSFrameLayout::Size() + topFrame->prevFrameLocalSize() + michael@0: IonBaselineStubFrameLayout::reverseOffsetOfSavedFramePtr(); michael@0: return virtualPointerAtStackOffset(offset); michael@0: } michael@0: michael@0: JS_ASSERT(type == JitFrame_Rectifier); michael@0: // Rectifier - behaviour depends on the frame preceding the rectifier frame, and michael@0: // whether the arch is x86 or not. The x86 rectifier frame saves the frame pointer, michael@0: // so we can calculate it directly. For other archs, the previous frame pointer michael@0: // is stored on the stack in the frame that precedes the rectifier frame. michael@0: size_t priorOffset = IonJSFrameLayout::Size() + topFrame->prevFrameLocalSize(); michael@0: #if defined(JS_CODEGEN_X86) michael@0: // On X86, the FramePointer is pushed as the first value in the Rectifier frame. michael@0: JS_ASSERT(BaselineFrameReg == FramePointer); michael@0: priorOffset -= sizeof(void *); michael@0: return virtualPointerAtStackOffset(priorOffset); michael@0: #elif defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS) michael@0: // On X64, ARM and MIPS, the frame pointer save location depends on michael@0: // the caller of the rectifier frame. michael@0: BufferPointer priorFrame = michael@0: pointerAtStackOffset(priorOffset); michael@0: FrameType priorType = priorFrame->prevType(); michael@0: JS_ASSERT(priorType == JitFrame_IonJS || priorType == JitFrame_BaselineStub); michael@0: michael@0: // If the frame preceding the rectifier is an IonJS frame, then once again michael@0: // the frame pointer does not matter. michael@0: if (priorType == JitFrame_IonJS) michael@0: return nullptr; michael@0: michael@0: // Otherwise, the frame preceding the rectifier is a BaselineStub frame. michael@0: // let X = STACK_START_ADDR + IonJSFrameLayout::Size() + PREV_FRAME_SIZE michael@0: // X + IonRectifierFrameLayout::Size() michael@0: // + ((IonRectifierFrameLayout *) X)->prevFrameLocalSize() michael@0: // - BaselineStubFrameLayout::reverseOffsetOfSavedFramePtr() michael@0: size_t extraOffset = IonRectifierFrameLayout::Size() + priorFrame->prevFrameLocalSize() + michael@0: IonBaselineStubFrameLayout::reverseOffsetOfSavedFramePtr(); michael@0: return virtualPointerAtStackOffset(priorOffset + extraOffset); michael@0: #else michael@0: # error "Bad architecture!" michael@0: #endif michael@0: } michael@0: }; michael@0: michael@0: static inline bool michael@0: IsInlinableFallback(ICFallbackStub *icEntry) michael@0: { michael@0: return icEntry->isCall_Fallback() || icEntry->isGetProp_Fallback() || michael@0: icEntry->isSetProp_Fallback(); michael@0: } michael@0: michael@0: static inline void* michael@0: GetStubReturnAddress(JSContext *cx, jsbytecode *pc) michael@0: { michael@0: if (IsGetPropPC(pc)) michael@0: return cx->compartment()->jitCompartment()->baselineGetPropReturnFromIonAddr(); michael@0: if (IsSetPropPC(pc)) michael@0: return cx->compartment()->jitCompartment()->baselineSetPropReturnFromIonAddr(); michael@0: // This should be a call op of some kind, now. michael@0: JS_ASSERT(IsCallPC(pc)); michael@0: return cx->compartment()->jitCompartment()->baselineCallReturnFromIonAddr(); michael@0: } michael@0: michael@0: static inline jsbytecode * michael@0: GetNextNonLoopEntryPc(jsbytecode *pc) michael@0: { michael@0: JSOp op = JSOp(*pc); michael@0: if (op == JSOP_GOTO) michael@0: return pc + GET_JUMP_OFFSET(pc); michael@0: if (op == JSOP_LOOPENTRY || op == JSOP_NOP || op == JSOP_LOOPHEAD) michael@0: return GetNextPc(pc); michael@0: return pc; michael@0: } michael@0: michael@0: // For every inline frame, we write out the following data: michael@0: // michael@0: // | ... | michael@0: // +---------------+ michael@0: // | Descr(???) | --- Descr size here is (PREV_FRAME_SIZE) michael@0: // +---------------+ michael@0: // | ReturnAddr | michael@0: // -- +===============+ --- OVERWRITE STARTS HERE (START_STACK_ADDR) michael@0: // | | PrevFramePtr | michael@0: // | +-> +---------------+ michael@0: // | | | Baseline | michael@0: // | | | Frame | michael@0: // | | +---------------+ michael@0: // | | | Fixed0 | michael@0: // | | +---------------+ michael@0: // +--< | | ... | michael@0: // | | | +---------------+ michael@0: // | | | | FixedF | michael@0: // | | | +---------------+ michael@0: // | | | | Stack0 | michael@0: // | | | +---------------+ michael@0: // | | | | ... | michael@0: // | | | +---------------+ michael@0: // | | | | StackS | michael@0: // | -- | +---------------+ --- IF NOT LAST INLINE FRAME, michael@0: // +------------| Descr(BLJS) | --- CALLING INFO STARTS HERE michael@0: // | +---------------+ michael@0: // | | ReturnAddr | <-- return into main jitcode after IC michael@0: // -- | +===============+ michael@0: // | | | StubPtr | michael@0: // | | +---------------+ michael@0: // | +---| FramePtr | michael@0: // | +---------------+ michael@0: // | | ArgA | michael@0: // | +---------------+ michael@0: // | | ... | michael@0: // +--< +---------------+ michael@0: // | | | Arg0 | michael@0: // | | +---------------+ michael@0: // | | | ThisV | michael@0: // | -- +---------------+ michael@0: // | | ActualArgC | michael@0: // | +---------------+ michael@0: // | | CalleeToken | michael@0: // | +---------------+ michael@0: // +------------| Descr(BLStub) | michael@0: // +---------------+ michael@0: // | ReturnAddr | <-- return into ICCall_Scripted IC michael@0: // -- +===============+ --- IF CALLEE FORMAL ARGS > ActualArgC michael@0: // | | UndefinedU | michael@0: // | +---------------+ michael@0: // | | ... | michael@0: // | +---------------+ michael@0: // | | Undefined0 | michael@0: // | +---------------+ michael@0: // +--< | ArgA | michael@0: // | | +---------------+ michael@0: // | | | ... | michael@0: // | | +---------------+ michael@0: // | | | Arg0 | michael@0: // | | +---------------+ michael@0: // | | | ThisV | michael@0: // | -- +---------------+ michael@0: // | | ActualArgC | michael@0: // | +---------------+ michael@0: // | | CalleeToken | michael@0: // | +---------------+ michael@0: // +------------| Descr(Rect) | michael@0: // +---------------+ michael@0: // | ReturnAddr | <-- return into ArgumentsRectifier after call michael@0: // +===============+ michael@0: // michael@0: static bool michael@0: InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC, michael@0: HandleFunction fun, HandleScript script, IonScript *ionScript, michael@0: SnapshotIterator &iter, bool invalidate, BaselineStackBuilder &builder, michael@0: AutoValueVector &startFrameFormals, MutableHandleFunction nextCallee, michael@0: jsbytecode **callPC, const ExceptionBailoutInfo *excInfo) michael@0: { michael@0: MOZ_ASSERT(script->hasBaselineScript()); michael@0: michael@0: // Are we catching an exception? michael@0: bool catchingException = excInfo && excInfo->catchingException(); michael@0: michael@0: // If we are catching an exception, we are bailing out to a catch or michael@0: // finally block and this is the frame where we will resume. Usually the michael@0: // expression stack should be empty in this case but there can be michael@0: // iterators on the stack. michael@0: uint32_t exprStackSlots; michael@0: if (catchingException) michael@0: exprStackSlots = excInfo->numExprSlots(); michael@0: else michael@0: exprStackSlots = iter.numAllocations() - (script->nfixed() + CountArgSlots(script, fun)); michael@0: michael@0: builder.resetFramePushed(); michael@0: michael@0: // Build first baseline frame: michael@0: // +===============+ michael@0: // | PrevFramePtr | michael@0: // +---------------+ michael@0: // | Baseline | michael@0: // | Frame | michael@0: // +---------------+ michael@0: // | Fixed0 | michael@0: // +---------------+ michael@0: // | ... | michael@0: // +---------------+ michael@0: // | FixedF | michael@0: // +---------------+ michael@0: // | Stack0 | michael@0: // +---------------+ michael@0: // | ... | michael@0: // +---------------+ michael@0: // | StackS | michael@0: // +---------------+ --- IF NOT LAST INLINE FRAME, michael@0: // | Descr(BLJS) | --- CALLING INFO STARTS HERE michael@0: // +---------------+ michael@0: // | ReturnAddr | <-- return into main jitcode after IC michael@0: // +===============+ michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, " Unpacking %s:%d", script->filename(), script->lineno()); michael@0: IonSpew(IonSpew_BaselineBailouts, " [BASELINE-JS FRAME]"); michael@0: michael@0: // Calculate and write the previous frame pointer value. michael@0: // Record the virtual stack offset at this location. Later on, if we end up michael@0: // writing out a BaselineStub frame for the next callee, we'll need to save the michael@0: // address. michael@0: void *prevFramePtr = builder.calculatePrevFramePtr(); michael@0: if (!builder.writePtr(prevFramePtr, "PrevFramePtr")) michael@0: return false; michael@0: prevFramePtr = builder.virtualPointerAtStackOffset(0); michael@0: michael@0: // Write struct BaselineFrame. michael@0: if (!builder.subtract(BaselineFrame::Size(), "BaselineFrame")) michael@0: return false; michael@0: BufferPointer blFrame = builder.pointerAtStackOffset(0); michael@0: michael@0: // Initialize BaselineFrame::frameSize michael@0: uint32_t frameSize = BaselineFrame::Size() + BaselineFrame::FramePointerOffset + michael@0: (sizeof(Value) * (script->nfixed() + exprStackSlots)); michael@0: IonSpew(IonSpew_BaselineBailouts, " FrameSize=%d", (int) frameSize); michael@0: blFrame->setFrameSize(frameSize); michael@0: michael@0: uint32_t flags = 0; michael@0: michael@0: // If SPS Profiler is enabled, mark the frame as having pushed an SPS entry. michael@0: // This may be wrong for the last frame of ArgumentCheck bailout, but michael@0: // that will be fixed later. michael@0: if (ionScript->hasSPSInstrumentation()) { michael@0: if (callerPC == nullptr) { michael@0: IonSpew(IonSpew_BaselineBailouts, " Setting SPS flag on top frame!"); michael@0: flags |= BaselineFrame::HAS_PUSHED_SPS_FRAME; michael@0: } else if (js_JitOptions.profileInlineFrames) { michael@0: IonSpew(IonSpew_BaselineBailouts, " Setting SPS flag on inline frame!"); michael@0: flags |= BaselineFrame::HAS_PUSHED_SPS_FRAME; michael@0: } michael@0: } michael@0: michael@0: // Initialize BaselineFrame's scopeChain and argsObj michael@0: JSObject *scopeChain = nullptr; michael@0: Value returnValue; michael@0: ArgumentsObject *argsObj = nullptr; michael@0: BailoutKind bailoutKind = iter.bailoutKind(); michael@0: if (bailoutKind == Bailout_ArgumentCheck) { michael@0: // Temporary hack -- skip the (unused) scopeChain, because it could be michael@0: // bogus (we can fail before the scope chain slot is set). Strip the michael@0: // hasScopeChain flag and this will be fixed up later in |FinishBailoutToBaseline|, michael@0: // which calls |EnsureHasScopeObjects|. michael@0: IonSpew(IonSpew_BaselineBailouts, " Bailout_ArgumentCheck! (no valid scopeChain)"); michael@0: iter.skip(); michael@0: michael@0: // skip |return value| michael@0: iter.skip(); michael@0: returnValue = UndefinedValue(); michael@0: michael@0: // Scripts with |argumentsHasVarBinding| have an extra slot. michael@0: if (script->argumentsHasVarBinding()) { michael@0: IonSpew(IonSpew_BaselineBailouts, michael@0: " Bailout_ArgumentCheck for script with argumentsHasVarBinding!" michael@0: "Using empty arguments object"); michael@0: iter.skip(); michael@0: } michael@0: } else { michael@0: Value v = iter.read(); michael@0: if (v.isObject()) { michael@0: scopeChain = &v.toObject(); michael@0: if (fun && fun->isHeavyweight()) michael@0: flags |= BaselineFrame::HAS_CALL_OBJ; michael@0: } else { michael@0: JS_ASSERT(v.isUndefined() || v.isMagic(JS_OPTIMIZED_OUT)); michael@0: michael@0: // Get scope chain from function or script. michael@0: if (fun) { michael@0: // If pcOffset == 0, we may have to push a new call object, so michael@0: // we leave scopeChain nullptr and enter baseline code before michael@0: // the prologue. michael@0: if (iter.pcOffset() != 0 || iter.resumeAfter()) michael@0: scopeChain = fun->environment(); michael@0: } else { michael@0: // For global, compile-and-go scripts the scope chain is the michael@0: // script's global (Ion does not compile non-compile-and-go michael@0: // scripts). Also note that it's invalid to resume into the michael@0: // prologue in this case because the prologue expects the scope michael@0: // chain in R1 for eval and global scripts. michael@0: JS_ASSERT(!script->isForEval()); michael@0: JS_ASSERT(script->compileAndGo()); michael@0: scopeChain = &(script->global()); michael@0: } michael@0: } michael@0: michael@0: // Make sure to add HAS_RVAL to flags here because setFlags() below michael@0: // will clobber it. michael@0: returnValue = iter.read(); michael@0: flags |= BaselineFrame::HAS_RVAL; michael@0: michael@0: // If script maybe has an arguments object, the third slot will hold it. michael@0: if (script->argumentsHasVarBinding()) { michael@0: v = iter.read(); michael@0: JS_ASSERT(v.isObject() || v.isUndefined() || v.isMagic(JS_OPTIMIZED_OUT)); michael@0: if (v.isObject()) michael@0: argsObj = &v.toObject().as(); michael@0: } michael@0: } michael@0: IonSpew(IonSpew_BaselineBailouts, " ScopeChain=%p", scopeChain); michael@0: blFrame->setScopeChain(scopeChain); michael@0: IonSpew(IonSpew_BaselineBailouts, " ReturnValue=%016llx", *((uint64_t *) &returnValue)); michael@0: blFrame->setReturnValue(returnValue); michael@0: michael@0: // Do not need to initialize scratchValue field in BaselineFrame. michael@0: blFrame->setFlags(flags); michael@0: michael@0: // initArgsObjUnchecked modifies the frame's flags, so call it after setFlags. michael@0: if (argsObj) michael@0: blFrame->initArgsObjUnchecked(*argsObj); michael@0: michael@0: if (fun) { michael@0: // The unpacked thisv and arguments should overwrite the pushed args present michael@0: // in the calling frame. michael@0: Value thisv = iter.read(); michael@0: IonSpew(IonSpew_BaselineBailouts, " Is function!"); michael@0: IonSpew(IonSpew_BaselineBailouts, " thisv=%016llx", *((uint64_t *) &thisv)); michael@0: michael@0: size_t thisvOffset = builder.framePushed() + IonJSFrameLayout::offsetOfThis(); michael@0: *builder.valuePointerAtStackOffset(thisvOffset) = thisv; michael@0: michael@0: JS_ASSERT(iter.numAllocations() >= CountArgSlots(script, fun)); michael@0: IonSpew(IonSpew_BaselineBailouts, " frame slots %u, nargs %u, nfixed %u", michael@0: iter.numAllocations(), fun->nargs(), script->nfixed()); michael@0: michael@0: if (!callerPC) { michael@0: // This is the first frame. Store the formals in a Vector until we michael@0: // are done. Due to UCE and phi elimination, we could store an michael@0: // UndefinedValue() here for formals we think are unused, but michael@0: // locals may still reference the original argument slot michael@0: // (MParameter/LArgument) and expect the original Value. michael@0: JS_ASSERT(startFrameFormals.empty()); michael@0: if (!startFrameFormals.resize(fun->nargs())) michael@0: return false; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < fun->nargs(); i++) { michael@0: Value arg = iter.read(); michael@0: IonSpew(IonSpew_BaselineBailouts, " arg %d = %016llx", michael@0: (int) i, *((uint64_t *) &arg)); michael@0: if (callerPC) { michael@0: size_t argOffset = builder.framePushed() + IonJSFrameLayout::offsetOfActualArg(i); michael@0: *builder.valuePointerAtStackOffset(argOffset) = arg; michael@0: } else { michael@0: startFrameFormals[i] = arg; michael@0: } michael@0: } michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < script->nfixed(); i++) { michael@0: Value slot = iter.read(); michael@0: if (!builder.writeValue(slot, "FixedValue")) michael@0: return false; michael@0: } michael@0: michael@0: // Get the pc. If we are handling an exception, resume at the pc of the michael@0: // catch or finally block. michael@0: jsbytecode *pc = catchingException ? excInfo->resumePC() : script->offsetToPC(iter.pcOffset()); michael@0: bool resumeAfter = catchingException ? false : iter.resumeAfter(); michael@0: michael@0: JSOp op = JSOp(*pc); michael@0: michael@0: // Fixup inlined JSOP_FUNCALL, JSOP_FUNAPPLY, and accessors on the caller side. michael@0: // On the caller side this must represent like the function wasn't inlined. michael@0: uint32_t pushedSlots = 0; michael@0: AutoValueVector savedCallerArgs(cx); michael@0: bool needToSaveArgs = op == JSOP_FUNAPPLY || IsGetPropPC(pc) || IsSetPropPC(pc); michael@0: if (iter.moreFrames() && (op == JSOP_FUNCALL || needToSaveArgs)) michael@0: { michael@0: uint32_t inlined_args = 0; michael@0: if (op == JSOP_FUNCALL) michael@0: inlined_args = 2 + GET_ARGC(pc) - 1; michael@0: else if (op == JSOP_FUNAPPLY) michael@0: inlined_args = 2 + blFrame->numActualArgs(); michael@0: else michael@0: inlined_args = 2 + IsSetPropPC(pc); michael@0: michael@0: JS_ASSERT(exprStackSlots >= inlined_args); michael@0: pushedSlots = exprStackSlots - inlined_args; michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, michael@0: " pushing %u expression stack slots before fixup", michael@0: pushedSlots); michael@0: for (uint32_t i = 0; i < pushedSlots; i++) { michael@0: Value v = iter.read(); michael@0: if (!builder.writeValue(v, "StackValue")) michael@0: return false; michael@0: } michael@0: michael@0: if (op == JSOP_FUNCALL) { michael@0: // When funcall got inlined and the native js_fun_call was bypassed, michael@0: // the stack state is incorrect. To restore correctly it must look like michael@0: // js_fun_call was actually called. This means transforming the stack michael@0: // from |target, this, args| to |js_fun_call, target, this, args| michael@0: // The js_fun_call is never read, so just pushing undefined now. michael@0: IonSpew(IonSpew_BaselineBailouts, " pushing undefined to fixup funcall"); michael@0: if (!builder.writeValue(UndefinedValue(), "StackValue")) michael@0: return false; michael@0: } michael@0: michael@0: if (needToSaveArgs) { michael@0: // When an accessor is inlined, the whole thing is a lie. There michael@0: // should never have been a call there. Fix the caller's stack to michael@0: // forget it ever happened. michael@0: michael@0: // When funapply gets inlined we take all arguments out of the michael@0: // arguments array. So the stack state is incorrect. To restore michael@0: // correctly it must look like js_fun_apply was actually called. michael@0: // This means transforming the stack from |target, this, arg1, ...| michael@0: // to |js_fun_apply, target, this, argObject|. michael@0: // Since the information is never read, we can just push undefined michael@0: // for all values. michael@0: if (op == JSOP_FUNAPPLY) { michael@0: IonSpew(IonSpew_BaselineBailouts, " pushing 4x undefined to fixup funapply"); michael@0: if (!builder.writeValue(UndefinedValue(), "StackValue")) michael@0: return false; michael@0: if (!builder.writeValue(UndefinedValue(), "StackValue")) michael@0: return false; michael@0: if (!builder.writeValue(UndefinedValue(), "StackValue")) michael@0: return false; michael@0: if (!builder.writeValue(UndefinedValue(), "StackValue")) michael@0: return false; michael@0: } michael@0: // Save the actual arguments. They are needed on the callee side michael@0: // as the arguments. Else we can't recover them. michael@0: if (!savedCallerArgs.resize(inlined_args)) michael@0: return false; michael@0: for (uint32_t i = 0; i < inlined_args; i++) michael@0: savedCallerArgs[i] = iter.read(); michael@0: michael@0: if (IsSetPropPC(pc)) { michael@0: // We would love to just save all the arguments and leave them michael@0: // in the stub frame pushed below, but we will lose the inital michael@0: // argument which the function was called with, which we must michael@0: // return to the caller, even if the setter internally modifies michael@0: // its arguments. Stash the initial argument on the stack, to be michael@0: // later retrieved by the SetProp_Fallback stub. michael@0: Value initialArg = savedCallerArgs[inlined_args - 1]; michael@0: IonSpew(IonSpew_BaselineBailouts, " pushing setter's initial argument"); michael@0: if (!builder.writeValue(initialArg, "StackValue")) michael@0: return false; michael@0: } michael@0: pushedSlots = exprStackSlots; michael@0: } michael@0: } michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, " pushing %u expression stack slots", michael@0: exprStackSlots - pushedSlots); michael@0: for (uint32_t i = pushedSlots; i < exprStackSlots; i++) { michael@0: Value v; michael@0: michael@0: if (!iter.moreFrames() && i == exprStackSlots - 1 && michael@0: cx->runtime()->hasIonReturnOverride()) michael@0: { michael@0: // If coming from an invalidation bailout, and this is the topmost michael@0: // value, and a value override has been specified, don't read from the michael@0: // iterator. Otherwise, we risk using a garbage value. michael@0: JS_ASSERT(invalidate); michael@0: iter.skip(); michael@0: IonSpew(IonSpew_BaselineBailouts, " [Return Override]"); michael@0: v = cx->runtime()->takeIonReturnOverride(); michael@0: } else if (excInfo && excInfo->propagatingIonExceptionForDebugMode()) { michael@0: // If we are in the middle of propagating an exception from Ion by michael@0: // bailing to baseline due to debug mode, we might not have all michael@0: // the stack if we are at the newest frame. michael@0: // michael@0: // For instance, if calling |f()| pushed an Ion frame which threw, michael@0: // the snapshot expects the return value to be pushed, but it's michael@0: // possible nothing was pushed before we threw. Iterators might michael@0: // still be on the stack, so we can't just drop the stack. michael@0: MOZ_ASSERT(cx->compartment()->debugMode()); michael@0: if (iter.moreFrames()) michael@0: v = iter.read(); michael@0: else michael@0: v = MagicValue(JS_OPTIMIZED_OUT); michael@0: } else { michael@0: v = iter.read(); michael@0: } michael@0: if (!builder.writeValue(v, "StackValue")) michael@0: return false; michael@0: } michael@0: michael@0: size_t endOfBaselineJSFrameStack = builder.framePushed(); michael@0: michael@0: // If we are resuming at a LOOPENTRY op, resume at the next op to avoid michael@0: // a bailout -> enter Ion -> bailout loop with --ion-eager. See also michael@0: // ThunkToInterpreter. michael@0: // michael@0: // The algorithm below is the "tortoise and the hare" algorithm. See bug michael@0: // 994444 for more explanation. michael@0: if (!resumeAfter) { michael@0: jsbytecode *fasterPc = pc; michael@0: while (true) { michael@0: pc = GetNextNonLoopEntryPc(pc); michael@0: fasterPc = GetNextNonLoopEntryPc(GetNextNonLoopEntryPc(fasterPc)); michael@0: if (fasterPc == pc) michael@0: break; michael@0: } michael@0: op = JSOp(*pc); michael@0: } michael@0: michael@0: uint32_t pcOff = script->pcToOffset(pc); michael@0: bool isCall = IsCallPC(pc); michael@0: BaselineScript *baselineScript = script->baselineScript(); michael@0: michael@0: #ifdef DEBUG michael@0: uint32_t expectedDepth; michael@0: bool reachablePC; michael@0: if (!ReconstructStackDepth(cx, script, resumeAfter ? GetNextPc(pc) : pc, &expectedDepth, &reachablePC)) michael@0: return false; michael@0: michael@0: if (reachablePC) { michael@0: if (op != JSOP_FUNAPPLY || !iter.moreFrames() || resumeAfter) { michael@0: if (op == JSOP_FUNCALL) { michael@0: // For fun.call(this, ...); the reconstructStackDepth will michael@0: // include the this. When inlining that is not included. michael@0: // So the exprStackSlots will be one less. michael@0: JS_ASSERT(expectedDepth - exprStackSlots <= 1); michael@0: } else if (iter.moreFrames() && (IsGetPropPC(pc) || IsSetPropPC(pc))) { michael@0: // Accessors coming out of ion are inlined via a complete michael@0: // lie perpetrated by the compiler internally. Ion just rearranges michael@0: // the stack, and pretends that it looked like a call all along. michael@0: // This means that the depth is actually one *more* than expected michael@0: // by the interpreter, as there is now a JSFunction, |this| and [arg], michael@0: // rather than the expected |this| and [arg] michael@0: // Note that none of that was pushed, but it's still reflected michael@0: // in exprStackSlots. michael@0: JS_ASSERT(exprStackSlots - expectedDepth == 1); michael@0: } else { michael@0: // For fun.apply({}, arguments) the reconstructStackDepth will michael@0: // have stackdepth 4, but it could be that we inlined the michael@0: // funapply. In that case exprStackSlots, will have the real michael@0: // arguments in the slots and not be 4. michael@0: JS_ASSERT(exprStackSlots == expectedDepth); michael@0: } michael@0: } michael@0: } michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, " Resuming %s pc offset %d (op %s) (line %d) of %s:%d", michael@0: resumeAfter ? "after" : "at", (int) pcOff, js_CodeName[op], michael@0: PCToLineNumber(script, pc), script->filename(), (int) script->lineno()); michael@0: IonSpew(IonSpew_BaselineBailouts, " Bailout kind: %s", michael@0: BailoutKindString(bailoutKind)); michael@0: #endif michael@0: michael@0: // If this was the last inline frame, or we are bailing out to a catch or michael@0: // finally block in this frame, then unpacking is almost done. michael@0: if (!iter.moreFrames() || catchingException) { michael@0: // Last frame, so PC for call to next frame is set to nullptr. michael@0: *callPC = nullptr; michael@0: michael@0: // If the bailout was a resumeAfter, and the opcode is monitored, michael@0: // then the bailed out state should be in a position to enter michael@0: // into the ICTypeMonitor chain for the op. michael@0: bool enterMonitorChain = false; michael@0: if (resumeAfter && (js_CodeSpec[op].format & JOF_TYPESET)) { michael@0: // Not every monitored op has a monitored fallback stub, e.g. michael@0: // JSOP_NEWOBJECT, which always returns the same type for a michael@0: // particular script/pc location. michael@0: ICEntry &icEntry = baselineScript->icEntryFromPCOffset(pcOff); michael@0: ICFallbackStub *fallbackStub = icEntry.firstStub()->getChainFallback(); michael@0: if (fallbackStub->isMonitoredFallback()) michael@0: enterMonitorChain = true; michael@0: } michael@0: michael@0: uint32_t numCallArgs = isCall ? GET_ARGC(pc) : 0; michael@0: michael@0: if (resumeAfter && !enterMonitorChain) michael@0: pc = GetNextPc(pc); michael@0: michael@0: builder.setResumeFramePtr(prevFramePtr); michael@0: michael@0: if (enterMonitorChain) { michael@0: ICEntry &icEntry = baselineScript->icEntryFromPCOffset(pcOff); michael@0: ICFallbackStub *fallbackStub = icEntry.firstStub()->getChainFallback(); michael@0: JS_ASSERT(fallbackStub->isMonitoredFallback()); michael@0: IonSpew(IonSpew_BaselineBailouts, " [TYPE-MONITOR CHAIN]"); michael@0: ICMonitoredFallbackStub *monFallbackStub = fallbackStub->toMonitoredFallbackStub(); michael@0: ICStub *firstMonStub = monFallbackStub->fallbackMonitorStub()->firstMonitorStub(); michael@0: michael@0: // To enter a monitoring chain, we load the top stack value into R0 michael@0: IonSpew(IonSpew_BaselineBailouts, " Popping top stack value into R0."); michael@0: builder.popValueInto(PCMappingSlotInfo::SlotInR0); michael@0: michael@0: // Need to adjust the frameSize for the frame to match the values popped michael@0: // into registers. michael@0: frameSize -= sizeof(Value); michael@0: blFrame->setFrameSize(frameSize); michael@0: IonSpew(IonSpew_BaselineBailouts, " Adjusted framesize -= %d: %d", michael@0: (int) sizeof(Value), (int) frameSize); michael@0: michael@0: // If resuming into a JSOP_CALL, baseline keeps the arguments on the michael@0: // stack and pops them only after returning from the call IC. michael@0: // Push undefs onto the stack in anticipation of the popping of the michael@0: // callee, thisv, and actual arguments passed from the caller's frame. michael@0: if (isCall) { michael@0: builder.writeValue(UndefinedValue(), "CallOp FillerCallee"); michael@0: builder.writeValue(UndefinedValue(), "CallOp FillerThis"); michael@0: for (uint32_t i = 0; i < numCallArgs; i++) michael@0: builder.writeValue(UndefinedValue(), "CallOp FillerArg"); michael@0: michael@0: frameSize += (numCallArgs + 2) * sizeof(Value); michael@0: blFrame->setFrameSize(frameSize); michael@0: IonSpew(IonSpew_BaselineBailouts, " Adjusted framesize += %d: %d", michael@0: (int) ((numCallArgs + 2) * sizeof(Value)), (int) frameSize); michael@0: } michael@0: michael@0: // Set the resume address to the return point from the IC, and set michael@0: // the monitor stub addr. michael@0: builder.setResumeAddr(baselineScript->returnAddressForIC(icEntry)); michael@0: builder.setMonitorStub(firstMonStub); michael@0: IonSpew(IonSpew_BaselineBailouts, " Set resumeAddr=%p monitorStub=%p", michael@0: baselineScript->returnAddressForIC(icEntry), firstMonStub); michael@0: michael@0: } else { michael@0: // If needed, initialize BaselineBailoutInfo's valueR0 and/or valueR1 with the michael@0: // top stack values. michael@0: PCMappingSlotInfo slotInfo; michael@0: uint8_t *nativeCodeForPC = baselineScript->nativeCodeForPC(script, pc, &slotInfo); michael@0: unsigned numUnsynced = slotInfo.numUnsynced(); michael@0: JS_ASSERT(numUnsynced <= 2); michael@0: PCMappingSlotInfo::SlotLocation loc1, loc2; michael@0: if (numUnsynced > 0) { michael@0: loc1 = slotInfo.topSlotLocation(); michael@0: IonSpew(IonSpew_BaselineBailouts, " Popping top stack value into %d.", michael@0: (int) loc1); michael@0: builder.popValueInto(loc1); michael@0: } michael@0: if (numUnsynced > 1) { michael@0: loc2 = slotInfo.nextSlotLocation(); michael@0: IonSpew(IonSpew_BaselineBailouts, " Popping next stack value into %d.", michael@0: (int) loc2); michael@0: JS_ASSERT_IF(loc1 != PCMappingSlotInfo::SlotIgnore, loc1 != loc2); michael@0: builder.popValueInto(loc2); michael@0: } michael@0: michael@0: // Need to adjust the frameSize for the frame to match the values popped michael@0: // into registers. michael@0: frameSize -= sizeof(Value) * numUnsynced; michael@0: blFrame->setFrameSize(frameSize); michael@0: IonSpew(IonSpew_BaselineBailouts, " Adjusted framesize -= %d: %d", michael@0: int(sizeof(Value) * numUnsynced), int(frameSize)); michael@0: michael@0: // If scopeChain is nullptr, then bailout is occurring during argument check. michael@0: // In this case, resume into the prologue. michael@0: uint8_t *opReturnAddr; michael@0: if (scopeChain == nullptr) { michael@0: // Global and eval scripts expect the scope chain in R1, so only michael@0: // resume into the prologue for function scripts. michael@0: JS_ASSERT(fun); michael@0: JS_ASSERT(numUnsynced == 0); michael@0: opReturnAddr = baselineScript->prologueEntryAddr(); michael@0: IonSpew(IonSpew_BaselineBailouts, " Resuming into prologue."); michael@0: michael@0: // If bailing into prologue, HAS_PUSHED_SPS_FRAME should not be set on frame. michael@0: blFrame->unsetPushedSPSFrame(); michael@0: michael@0: if (cx->runtime()->spsProfiler.enabled()) { michael@0: if (js_JitOptions.profileInlineFrames) { michael@0: // If SPS is enabled, there are two corner cases to handle: michael@0: // 1. If resuming into the prologue, and innermost frame is an inlined michael@0: // frame, and bailout is because of argument check failure, then: michael@0: // Top SPS profiler entry would be for caller frame. michael@0: // Ion would not have set the PC index field on that frame michael@0: // (since this bailout happens before MFunctionBoundary). michael@0: // Make sure that's done now. michael@0: // 2. If resuming into the prologue, and the bailout is NOT because of an michael@0: // argument check, then: michael@0: // Top SPS profiler entry would be for callee frame. michael@0: // Ion would already have pushed an SPS entry for this frame. michael@0: // The pc for this entry would be set to nullptr. michael@0: // Make sure it's set to script->pc. michael@0: if (caller && bailoutKind == Bailout_ArgumentCheck) { michael@0: IonSpew(IonSpew_BaselineBailouts, " Setting PCidx on innermost " michael@0: "inlined frame's parent's SPS entry (%s:%d) (pcIdx=%d)!", michael@0: caller->filename(), caller->lineno(), michael@0: caller->pcToOffset(callerPC)); michael@0: cx->runtime()->spsProfiler.updatePC(caller, callerPC); michael@0: michael@0: } else if (bailoutKind != Bailout_ArgumentCheck) { michael@0: IonSpew(IonSpew_BaselineBailouts, michael@0: " Popping SPS entry for innermost inlined frame"); michael@0: cx->runtime()->spsProfiler.exit(script, fun); michael@0: } michael@0: michael@0: } else { michael@0: // If not profiling inline frames, then this is logically simpler. michael@0: // michael@0: // 1. If resuming into inline code, then the top SPS entry will be michael@0: // for the outermost caller, and will have an uninitialized PC. michael@0: // This will be fixed up later in BailoutIonToBaseline. michael@0: // michael@0: // 2. If resuming into top-level code prologue, with ArgumentCheck, michael@0: // no SPS entry will have been pushed. Can be left alone. michael@0: // michael@0: // 3. If resuming into top-level code prologue, without ArgumentCheck, michael@0: // an SPS entry will have been pushed, and needs to be popped. michael@0: // michael@0: // 4. If resuming into top-level code main body, an SPS entry will michael@0: // have been pushed, and can be left alone. michael@0: // michael@0: // Only need to handle case 3 here. michael@0: if (!caller && bailoutKind != Bailout_ArgumentCheck) { michael@0: IonSpew(IonSpew_BaselineBailouts, michael@0: " Popping SPS entry for outermost frame"); michael@0: cx->runtime()->spsProfiler.exit(script, fun); michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: opReturnAddr = nativeCodeForPC; michael@0: } michael@0: builder.setResumeAddr(opReturnAddr); michael@0: IonSpew(IonSpew_BaselineBailouts, " Set resumeAddr=%p", opReturnAddr); michael@0: } michael@0: michael@0: if (cx->runtime()->spsProfiler.enabled()) { michael@0: if (blFrame->hasPushedSPSFrame()) { michael@0: // Set PC index to 0 for the innermost frame to match what the michael@0: // interpreter and Baseline do: they update the SPS pc for michael@0: // JSOP_CALL ops but set it to 0 when running other ops. Ion code michael@0: // can set the pc to NullPCIndex and this will confuse SPS when michael@0: // Baseline calls into the VM at non-CALL ops and re-enters JS. michael@0: IonSpew(IonSpew_BaselineBailouts, " Setting PCidx for last frame to 0"); michael@0: cx->runtime()->spsProfiler.updatePC(script, script->code()); michael@0: } michael@0: michael@0: // Register bailout with profiler. michael@0: const char *filename = script->filename(); michael@0: if (filename == nullptr) michael@0: filename = ""; michael@0: unsigned len = strlen(filename) + 200; michael@0: char *buf = js_pod_malloc(len); michael@0: if (buf == nullptr) michael@0: return false; michael@0: JS_snprintf(buf, len, "%s %s %s on line %d of %s:%d", michael@0: BailoutKindString(bailoutKind), michael@0: resumeAfter ? "after" : "at", michael@0: js_CodeName[op], michael@0: int(PCToLineNumber(script, pc)), michael@0: filename, michael@0: int(script->lineno())); michael@0: cx->runtime()->spsProfiler.markEvent(buf); michael@0: js_free(buf); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: *callPC = pc; michael@0: michael@0: // Write out descriptor of BaselineJS frame. michael@0: size_t baselineFrameDescr = MakeFrameDescriptor((uint32_t) builder.framePushed(), michael@0: JitFrame_BaselineJS); michael@0: if (!builder.writeWord(baselineFrameDescr, "Descriptor")) michael@0: return false; michael@0: michael@0: // Calculate and write out return address. michael@0: // The icEntry in question MUST have an inlinable fallback stub. michael@0: ICEntry &icEntry = baselineScript->icEntryFromPCOffset(pcOff); michael@0: JS_ASSERT(IsInlinableFallback(icEntry.firstStub()->getChainFallback())); michael@0: if (!builder.writePtr(baselineScript->returnAddressForIC(icEntry), "ReturnAddr")) michael@0: return false; michael@0: michael@0: // Build baseline stub frame: michael@0: // +===============+ michael@0: // | StubPtr | michael@0: // +---------------+ michael@0: // | FramePtr | michael@0: // +---------------+ michael@0: // | ArgA | michael@0: // +---------------+ michael@0: // | ... | michael@0: // +---------------+ michael@0: // | Arg0 | michael@0: // +---------------+ michael@0: // | ThisV | michael@0: // +---------------+ michael@0: // | ActualArgC | michael@0: // +---------------+ michael@0: // | CalleeToken | michael@0: // +---------------+ michael@0: // | Descr(BLStub) | michael@0: // +---------------+ michael@0: // | ReturnAddr | michael@0: // +===============+ michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, " [BASELINE-STUB FRAME]"); michael@0: michael@0: size_t startOfBaselineStubFrame = builder.framePushed(); michael@0: michael@0: // Write stub pointer. michael@0: JS_ASSERT(IsInlinableFallback(icEntry.fallbackStub())); michael@0: if (!builder.writePtr(icEntry.fallbackStub(), "StubPtr")) michael@0: return false; michael@0: michael@0: // Write previous frame pointer (saved earlier). michael@0: if (!builder.writePtr(prevFramePtr, "PrevFramePtr")) michael@0: return false; michael@0: prevFramePtr = builder.virtualPointerAtStackOffset(0); michael@0: michael@0: // Write out actual arguments (and thisv), copied from unpacked stack of BaselineJS frame. michael@0: // Arguments are reversed on the BaselineJS frame's stack values. michael@0: JS_ASSERT(IsIonInlinablePC(pc)); michael@0: unsigned actualArgc; michael@0: if (needToSaveArgs) { michael@0: // For FUNAPPLY or an accessor, the arguments are not on the stack anymore, michael@0: // but they are copied in a vector and are written here. michael@0: if (op == JSOP_FUNAPPLY) michael@0: actualArgc = blFrame->numActualArgs(); michael@0: else michael@0: actualArgc = IsSetPropPC(pc); michael@0: michael@0: JS_ASSERT(actualArgc + 2 <= exprStackSlots); michael@0: JS_ASSERT(savedCallerArgs.length() == actualArgc + 2); michael@0: for (unsigned i = 0; i < actualArgc + 1; i++) { michael@0: size_t arg = savedCallerArgs.length() - (i + 1); michael@0: if (!builder.writeValue(savedCallerArgs[arg], "ArgVal")) michael@0: return false; michael@0: } michael@0: } else { michael@0: actualArgc = GET_ARGC(pc); michael@0: if (op == JSOP_FUNCALL) { michael@0: JS_ASSERT(actualArgc > 0); michael@0: actualArgc--; michael@0: } michael@0: michael@0: JS_ASSERT(actualArgc + 2 <= exprStackSlots); michael@0: for (unsigned i = 0; i < actualArgc + 1; i++) { michael@0: size_t argSlot = (script->nfixed() + exprStackSlots) - (i + 1); michael@0: if (!builder.writeValue(*blFrame->valueSlot(argSlot), "ArgVal")) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // In case these arguments need to be copied on the stack again for a rectifier frame, michael@0: // save the framePushed values here for later use. michael@0: size_t endOfBaselineStubArgs = builder.framePushed(); michael@0: michael@0: // Calculate frame size for descriptor. michael@0: size_t baselineStubFrameSize = builder.framePushed() - startOfBaselineStubFrame; michael@0: size_t baselineStubFrameDescr = MakeFrameDescriptor((uint32_t) baselineStubFrameSize, michael@0: JitFrame_BaselineStub); michael@0: michael@0: // Push actual argc michael@0: if (!builder.writeWord(actualArgc, "ActualArgc")) michael@0: return false; michael@0: michael@0: // Push callee token (must be a JS Function) michael@0: Value callee; michael@0: if (needToSaveArgs) { michael@0: // The arguments of FUNAPPLY or inlined accessors are not writen to the stack. michael@0: // So get the callee from the specially saved vector. michael@0: callee = savedCallerArgs[0]; michael@0: } else { michael@0: uint32_t calleeStackSlot = exprStackSlots - uint32_t(actualArgc + 2); michael@0: size_t calleeOffset = (builder.framePushed() - endOfBaselineJSFrameStack) michael@0: + ((exprStackSlots - (calleeStackSlot + 1)) * sizeof(Value)); michael@0: callee = *builder.valuePointerAtStackOffset(calleeOffset); michael@0: IonSpew(IonSpew_BaselineBailouts, " CalleeStackSlot=%d", (int) calleeStackSlot); michael@0: } michael@0: IonSpew(IonSpew_BaselineBailouts, " Callee = %016llx", *((uint64_t *) &callee)); michael@0: JS_ASSERT(callee.isObject() && callee.toObject().is()); michael@0: JSFunction *calleeFun = &callee.toObject().as(); michael@0: if (!builder.writePtr(CalleeToToken(calleeFun), "CalleeToken")) michael@0: return false; michael@0: nextCallee.set(calleeFun); michael@0: michael@0: // Push BaselineStub frame descriptor michael@0: if (!builder.writeWord(baselineStubFrameDescr, "Descriptor")) michael@0: return false; michael@0: michael@0: // Push return address into ICCall_Scripted stub, immediately after the call. michael@0: void *baselineCallReturnAddr = GetStubReturnAddress(cx, pc); michael@0: JS_ASSERT(baselineCallReturnAddr); michael@0: if (!builder.writePtr(baselineCallReturnAddr, "ReturnAddr")) michael@0: return false; michael@0: michael@0: // If actualArgc >= fun->nargs, then we are done. Otherwise, we need to push on michael@0: // a reconstructed rectifier frame. michael@0: if (actualArgc >= calleeFun->nargs()) michael@0: return true; michael@0: michael@0: // Push a reconstructed rectifier frame. michael@0: // +===============+ michael@0: // | UndefinedU | michael@0: // +---------------+ michael@0: // | ... | michael@0: // +---------------+ michael@0: // | Undefined0 | michael@0: // +---------------+ michael@0: // | ArgA | michael@0: // +---------------+ michael@0: // | ... | michael@0: // +---------------+ michael@0: // | Arg0 | michael@0: // +---------------+ michael@0: // | ThisV | michael@0: // +---------------+ michael@0: // | ActualArgC | michael@0: // +---------------+ michael@0: // | CalleeToken | michael@0: // +---------------+ michael@0: // | Descr(Rect) | michael@0: // +---------------+ michael@0: // | ReturnAddr | michael@0: // +===============+ michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, " [RECTIFIER FRAME]"); michael@0: michael@0: size_t startOfRectifierFrame = builder.framePushed(); michael@0: michael@0: // On x86-only, the frame pointer is saved again in the rectifier frame. michael@0: #if defined(JS_CODEGEN_X86) michael@0: if (!builder.writePtr(prevFramePtr, "PrevFramePtr-X86Only")) michael@0: return false; michael@0: #endif michael@0: michael@0: // Push undefined for missing arguments. michael@0: for (unsigned i = 0; i < (calleeFun->nargs() - actualArgc); i++) { michael@0: if (!builder.writeValue(UndefinedValue(), "FillerVal")) michael@0: return false; michael@0: } michael@0: michael@0: // Copy arguments + thisv from BaselineStub frame. michael@0: if (!builder.subtract((actualArgc + 1) * sizeof(Value), "CopiedArgs")) michael@0: return false; michael@0: BufferPointer stubArgsEnd = michael@0: builder.pointerAtStackOffset(builder.framePushed() - endOfBaselineStubArgs); michael@0: IonSpew(IonSpew_BaselineBailouts, " MemCpy from %p", stubArgsEnd.get()); michael@0: memcpy(builder.pointerAtStackOffset(0).get(), stubArgsEnd.get(), michael@0: (actualArgc + 1) * sizeof(Value)); michael@0: michael@0: // Calculate frame size for descriptor. michael@0: size_t rectifierFrameSize = builder.framePushed() - startOfRectifierFrame; michael@0: size_t rectifierFrameDescr = MakeFrameDescriptor((uint32_t) rectifierFrameSize, michael@0: JitFrame_Rectifier); michael@0: michael@0: // Push actualArgc michael@0: if (!builder.writeWord(actualArgc, "ActualArgc")) michael@0: return false; michael@0: michael@0: // Push calleeToken again. michael@0: if (!builder.writePtr(CalleeToToken(calleeFun), "CalleeToken")) michael@0: return false; michael@0: michael@0: // Push rectifier frame descriptor michael@0: if (!builder.writeWord(rectifierFrameDescr, "Descriptor")) michael@0: return false; michael@0: michael@0: // Push return address into the ArgumentsRectifier code, immediately after the ioncode michael@0: // call. michael@0: void *rectReturnAddr = cx->runtime()->jitRuntime()->getArgumentsRectifierReturnAddr(); michael@0: JS_ASSERT(rectReturnAddr); michael@0: if (!builder.writePtr(rectReturnAddr, "ReturnAddr")) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: uint32_t michael@0: jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, IonBailoutIterator &iter, michael@0: bool invalidate, BaselineBailoutInfo **bailoutInfo, michael@0: const ExceptionBailoutInfo *excInfo) michael@0: { michael@0: JS_ASSERT(bailoutInfo != nullptr); michael@0: JS_ASSERT(*bailoutInfo == nullptr); michael@0: michael@0: TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); michael@0: TraceLogStopEvent(logger, TraceLogger::IonMonkey); michael@0: TraceLogStartEvent(logger, TraceLogger::Baseline); michael@0: michael@0: // The caller of the top frame must be one of the following: michael@0: // IonJS - Ion calling into Ion. michael@0: // BaselineStub - Baseline calling into Ion. michael@0: // Entry - Interpreter or other calling into Ion. michael@0: // Rectifier - Arguments rectifier calling into Ion. michael@0: JS_ASSERT(iter.isIonJS()); michael@0: FrameType prevFrameType = iter.prevType(); michael@0: JS_ASSERT(prevFrameType == JitFrame_IonJS || michael@0: prevFrameType == JitFrame_BaselineStub || michael@0: prevFrameType == JitFrame_Entry || michael@0: prevFrameType == JitFrame_Rectifier); michael@0: michael@0: // All incoming frames are going to look like this: michael@0: // michael@0: // +---------------+ michael@0: // | ... | michael@0: // +---------------+ michael@0: // | Args | michael@0: // | ... | michael@0: // +---------------+ michael@0: // | ThisV | michael@0: // +---------------+ michael@0: // | ActualArgC | michael@0: // +---------------+ michael@0: // | CalleeToken | michael@0: // +---------------+ michael@0: // | Descriptor | michael@0: // +---------------+ michael@0: // | ReturnAddr | michael@0: // +---------------+ michael@0: // | ||||| | <---- Overwrite starting here. michael@0: // | ||||| | michael@0: // | ||||| | michael@0: // +---------------+ michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, "Bailing to baseline %s:%u (IonScript=%p) (FrameType=%d)", michael@0: iter.script()->filename(), iter.script()->lineno(), (void *) iter.ionScript(), michael@0: (int) prevFrameType); michael@0: michael@0: bool catchingException; michael@0: bool propagatingExceptionForDebugMode; michael@0: if (excInfo) { michael@0: catchingException = excInfo->catchingException(); michael@0: propagatingExceptionForDebugMode = excInfo->propagatingIonExceptionForDebugMode(); michael@0: michael@0: if (catchingException) michael@0: IonSpew(IonSpew_BaselineBailouts, "Resuming in catch or finally block"); michael@0: michael@0: if (propagatingExceptionForDebugMode) michael@0: IonSpew(IonSpew_BaselineBailouts, "Resuming in-place for debug mode"); michael@0: } else { michael@0: catchingException = false; michael@0: propagatingExceptionForDebugMode = false; michael@0: } michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, " Reading from snapshot offset %u size %u", michael@0: iter.snapshotOffset(), iter.ionScript()->snapshotsListSize()); michael@0: michael@0: if (!excInfo) michael@0: iter.ionScript()->incNumBailouts(); michael@0: iter.script()->updateBaselineOrIonRaw(); michael@0: michael@0: // Allocate buffer to hold stack replacement data. michael@0: BaselineStackBuilder builder(iter, 1024); michael@0: if (!builder.init()) michael@0: return BAILOUT_RETURN_FATAL_ERROR; michael@0: IonSpew(IonSpew_BaselineBailouts, " Incoming frame ptr = %p", builder.startFrame()); michael@0: michael@0: SnapshotIterator snapIter(iter); michael@0: michael@0: RootedFunction callee(cx, iter.maybeCallee()); michael@0: RootedScript scr(cx, iter.script()); michael@0: if (callee) { michael@0: IonSpew(IonSpew_BaselineBailouts, " Callee function (%s:%u)", michael@0: scr->filename(), scr->lineno()); michael@0: } else { michael@0: IonSpew(IonSpew_BaselineBailouts, " No callee!"); michael@0: } michael@0: michael@0: if (iter.isConstructing()) michael@0: IonSpew(IonSpew_BaselineBailouts, " Constructing!"); michael@0: else michael@0: IonSpew(IonSpew_BaselineBailouts, " Not constructing!"); michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, " Restoring frames:"); michael@0: size_t frameNo = 0; michael@0: michael@0: // Reconstruct baseline frames using the builder. michael@0: RootedScript caller(cx); michael@0: jsbytecode *callerPC = nullptr; michael@0: RootedFunction fun(cx, callee); michael@0: AutoValueVector startFrameFormals(cx); michael@0: michael@0: RootedScript topCaller(cx); michael@0: jsbytecode *topCallerPC = nullptr; michael@0: michael@0: while (true) { michael@0: MOZ_ASSERT(snapIter.instruction()->isResumePoint()); michael@0: michael@0: if (frameNo > 0) { michael@0: TraceLogStartEvent(logger, TraceLogCreateTextId(logger, scr)); michael@0: TraceLogStartEvent(logger, TraceLogger::Baseline); michael@0: } michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, " FrameNo %d", frameNo); michael@0: michael@0: // If we are bailing out to a catch or finally block in this frame, michael@0: // pass excInfo to InitFromBailout and don't unpack any other frames. michael@0: bool handleException = (catchingException && excInfo->frameNo() == frameNo); michael@0: michael@0: // We also need to pass excInfo if we're bailing out in place for michael@0: // debug mode. michael@0: bool passExcInfo = handleException || propagatingExceptionForDebugMode; michael@0: michael@0: jsbytecode *callPC = nullptr; michael@0: RootedFunction nextCallee(cx, nullptr); michael@0: if (!InitFromBailout(cx, caller, callerPC, fun, scr, iter.ionScript(), michael@0: snapIter, invalidate, builder, startFrameFormals, michael@0: &nextCallee, &callPC, passExcInfo ? excInfo : nullptr)) michael@0: { michael@0: return BAILOUT_RETURN_FATAL_ERROR; michael@0: } michael@0: michael@0: if (!snapIter.moreFrames()) { michael@0: JS_ASSERT(!callPC); michael@0: break; michael@0: } michael@0: michael@0: if (handleException) michael@0: break; michael@0: michael@0: JS_ASSERT(nextCallee); michael@0: JS_ASSERT(callPC); michael@0: caller = scr; michael@0: callerPC = callPC; michael@0: fun = nextCallee; michael@0: scr = fun->existingScriptForInlinedFunction(); michael@0: michael@0: // Save top caller info for adjusting SPS frames later. michael@0: if (!topCaller) { michael@0: JS_ASSERT(frameNo == 0); michael@0: topCaller = caller; michael@0: topCallerPC = callerPC; michael@0: } michael@0: michael@0: frameNo++; michael@0: michael@0: snapIter.nextInstruction(); michael@0: } michael@0: IonSpew(IonSpew_BaselineBailouts, " Done restoring frames"); michael@0: michael@0: // If there were multiple inline frames unpacked, and inline frame profiling michael@0: // is off, then the current top SPS frame is for the outermost caller, and michael@0: // has an uninitialized PC. Initialize it now. michael@0: if (frameNo > 0 && !js_JitOptions.profileInlineFrames) michael@0: cx->runtime()->spsProfiler.updatePC(topCaller, topCallerPC); michael@0: michael@0: BailoutKind bailoutKind = snapIter.bailoutKind(); michael@0: michael@0: if (!startFrameFormals.empty()) { michael@0: // Set the first frame's formals, see the comment in InitFromBailout. michael@0: Value *argv = builder.startFrame()->argv() + 1; // +1 to skip |this|. michael@0: mozilla::PodCopy(argv, startFrameFormals.begin(), startFrameFormals.length()); michael@0: } michael@0: michael@0: // Do stack check. michael@0: bool overRecursed = false; michael@0: BaselineBailoutInfo *info = builder.info(); michael@0: uint8_t *newsp = info->incomingStack - (info->copyStackTop - info->copyStackBottom); michael@0: #ifdef JS_ARM_SIMULATOR michael@0: if (Simulator::Current()->overRecursed(uintptr_t(newsp))) michael@0: overRecursed = true; michael@0: #else michael@0: JS_CHECK_RECURSION_WITH_SP_DONT_REPORT(cx, newsp, overRecursed = true); michael@0: #endif michael@0: if (overRecursed) { michael@0: IonSpew(IonSpew_BaselineBailouts, " Overrecursion check failed!"); michael@0: return BAILOUT_RETURN_OVERRECURSED; michael@0: } michael@0: michael@0: // Take the reconstructed baseline stack so it doesn't get freed when builder destructs. michael@0: info = builder.takeBuffer(); michael@0: info->numFrames = frameNo + 1; michael@0: info->bailoutKind = bailoutKind; michael@0: *bailoutInfo = info; michael@0: return BAILOUT_RETURN_OK; michael@0: } michael@0: michael@0: static bool michael@0: HandleBoundsCheckFailure(JSContext *cx, HandleScript outerScript, HandleScript innerScript) michael@0: { michael@0: IonSpew(IonSpew_Bailouts, "Bounds check failure %s:%d, inlined into %s:%d", michael@0: innerScript->filename(), innerScript->lineno(), michael@0: outerScript->filename(), outerScript->lineno()); michael@0: michael@0: JS_ASSERT(!outerScript->ionScript()->invalidated()); michael@0: michael@0: // TODO: Currently this mimic's Ion's handling of this case. Investigate setting michael@0: // the flag on innerScript as opposed to outerScript, and maybe invalidating both michael@0: // inner and outer scripts, instead of just the outer one. michael@0: if (!outerScript->failedBoundsCheck()) michael@0: outerScript->setFailedBoundsCheck(); michael@0: IonSpew(IonSpew_BaselineBailouts, "Invalidating due to bounds check failure"); michael@0: return Invalidate(cx, outerScript); michael@0: } michael@0: michael@0: static bool michael@0: HandleShapeGuardFailure(JSContext *cx, HandleScript outerScript, HandleScript innerScript) michael@0: { michael@0: IonSpew(IonSpew_Bailouts, "Shape guard failure %s:%d, inlined into %s:%d", michael@0: innerScript->filename(), innerScript->lineno(), michael@0: outerScript->filename(), outerScript->lineno()); michael@0: michael@0: JS_ASSERT(!outerScript->ionScript()->invalidated()); michael@0: michael@0: // TODO: Currently this mimic's Ion's handling of this case. Investigate setting michael@0: // the flag on innerScript as opposed to outerScript, and maybe invalidating both michael@0: // inner and outer scripts, instead of just the outer one. michael@0: outerScript->setFailedShapeGuard(); michael@0: IonSpew(IonSpew_BaselineBailouts, "Invalidating due to shape guard failure"); michael@0: return Invalidate(cx, outerScript); michael@0: } michael@0: michael@0: static bool michael@0: HandleBaselineInfoBailout(JSContext *cx, JSScript *outerScript, JSScript *innerScript) michael@0: { michael@0: IonSpew(IonSpew_Bailouts, "Baseline info failure %s:%d, inlined into %s:%d", michael@0: innerScript->filename(), innerScript->lineno(), michael@0: outerScript->filename(), outerScript->lineno()); michael@0: michael@0: JS_ASSERT(!outerScript->ionScript()->invalidated()); michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, "Invalidating due to invalid baseline info"); michael@0: return Invalidate(cx, outerScript); michael@0: } michael@0: michael@0: static bool michael@0: CopyFromRematerializedFrame(JSContext *cx, JitActivation *act, uint8_t *fp, size_t inlineDepth, michael@0: BaselineFrame *frame) michael@0: { michael@0: RematerializedFrame *rematFrame = act->lookupRematerializedFrame(fp, inlineDepth); michael@0: michael@0: // We might not have rematerialized a frame if the user never requested a michael@0: // Debugger.Frame for it. michael@0: if (!rematFrame) michael@0: return true; michael@0: michael@0: MOZ_ASSERT(rematFrame->script() == frame->script()); michael@0: MOZ_ASSERT(rematFrame->numActualArgs() == frame->numActualArgs()); michael@0: michael@0: frame->setScopeChain(rematFrame->scopeChain()); michael@0: frame->thisValue() = rematFrame->thisValue(); michael@0: michael@0: for (unsigned i = 0; i < frame->numActualArgs(); i++) michael@0: frame->argv()[i] = rematFrame->argv()[i]; michael@0: michael@0: for (size_t i = 0; i < frame->script()->nfixed(); i++) michael@0: *frame->valueSlot(i) = rematFrame->locals()[i]; michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, michael@0: " Copied from rematerialized frame at (%p,%u)", michael@0: fp, inlineDepth); michael@0: michael@0: if (cx->compartment()->debugMode()) michael@0: return Debugger::handleIonBailout(cx, rematFrame, frame); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: uint32_t michael@0: jit::FinishBailoutToBaseline(BaselineBailoutInfo *bailoutInfo) michael@0: { michael@0: // The caller pushes R0 and R1 on the stack without rooting them. michael@0: // Since GC here is very unlikely just suppress it. michael@0: JSContext *cx = GetJSContextFromJitCode(); michael@0: js::gc::AutoSuppressGC suppressGC(cx); michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, " Done restoring frames"); michael@0: michael@0: // Check that we can get the current script's PC. michael@0: #ifdef DEBUG michael@0: jsbytecode *pc; michael@0: cx->currentScript(&pc); michael@0: IonSpew(IonSpew_BaselineBailouts, " Got pc=%p", pc); michael@0: #endif michael@0: michael@0: uint32_t numFrames = bailoutInfo->numFrames; michael@0: JS_ASSERT(numFrames > 0); michael@0: BailoutKind bailoutKind = bailoutInfo->bailoutKind; michael@0: michael@0: // Free the bailout buffer. michael@0: js_free(bailoutInfo); michael@0: bailoutInfo = nullptr; michael@0: michael@0: // Ensure the frame has a call object if it needs one. If the scope chain michael@0: // is nullptr, we will enter baseline code at the prologue so no need to do michael@0: // anything in that case. michael@0: BaselineFrame *topFrame = GetTopBaselineFrame(cx); michael@0: if (topFrame->scopeChain() && !EnsureHasScopeObjects(cx, topFrame)) michael@0: return false; michael@0: michael@0: // Create arguments objects for bailed out frames, to maintain the invariant michael@0: // that script->needsArgsObj() implies frame->hasArgsObj(). michael@0: RootedScript innerScript(cx, nullptr); michael@0: RootedScript outerScript(cx, nullptr); michael@0: michael@0: JS_ASSERT(cx->currentlyRunningInJit()); michael@0: JitFrameIterator iter(cx); michael@0: uint8_t *outerFp = nullptr; michael@0: michael@0: uint32_t frameno = 0; michael@0: while (frameno < numFrames) { michael@0: JS_ASSERT(!iter.isIonJS()); michael@0: michael@0: if (iter.isBaselineJS()) { michael@0: BaselineFrame *frame = iter.baselineFrame(); michael@0: MOZ_ASSERT(frame->script()->hasBaselineScript()); michael@0: michael@0: // If the frame doesn't even have a scope chain set yet, then it's resuming michael@0: // into the the prologue before the scope chain is initialized. Any michael@0: // necessary args object will also be initialized there. michael@0: if (frame->scopeChain() && frame->script()->needsArgsObj()) { michael@0: ArgumentsObject *argsObj; michael@0: if (frame->hasArgsObj()) { michael@0: argsObj = &frame->argsObj(); michael@0: } else { michael@0: argsObj = ArgumentsObject::createExpected(cx, frame); michael@0: if (!argsObj) michael@0: return false; michael@0: } michael@0: michael@0: // The arguments is a local binding and needsArgsObj does not michael@0: // check if it is clobbered. Ensure that the local binding michael@0: // restored during bailout before storing the arguments object michael@0: // to the slot. michael@0: RootedScript script(cx, frame->script()); michael@0: SetFrameArgumentsObject(cx, frame, script, argsObj); michael@0: } michael@0: michael@0: if (frameno == 0) michael@0: innerScript = frame->script(); michael@0: michael@0: if (frameno == numFrames - 1) { michael@0: outerScript = frame->script(); michael@0: outerFp = iter.fp(); michael@0: } michael@0: michael@0: frameno++; michael@0: } michael@0: michael@0: ++iter; michael@0: } michael@0: michael@0: MOZ_ASSERT(innerScript); michael@0: MOZ_ASSERT(outerScript); michael@0: MOZ_ASSERT(outerFp); michael@0: michael@0: // If we rematerialized Ion frames due to debug mode toggling, copy their michael@0: // values into the baseline frame. We need to do this even when debug mode michael@0: // is off, as we should respect the mutations made while debug mode was michael@0: // on. michael@0: JitActivation *act = cx->mainThread().activation()->asJit(); michael@0: if (act->hasRematerializedFrame(outerFp)) { michael@0: JitFrameIterator iter(cx); michael@0: size_t inlineDepth = numFrames; michael@0: while (inlineDepth > 0) { michael@0: if (iter.isBaselineJS() && michael@0: !CopyFromRematerializedFrame(cx, act, outerFp, --inlineDepth, michael@0: iter.baselineFrame())) michael@0: { michael@0: return false; michael@0: } michael@0: ++iter; michael@0: } michael@0: michael@0: // After copying from all the rematerialized frames, remove them from michael@0: // the table to keep the table up to date. michael@0: act->removeRematerializedFrame(outerFp); michael@0: } michael@0: michael@0: IonSpew(IonSpew_BaselineBailouts, michael@0: " Restored outerScript=(%s:%u,%u) innerScript=(%s:%u,%u) (bailoutKind=%u)", michael@0: outerScript->filename(), outerScript->lineno(), outerScript->getUseCount(), michael@0: innerScript->filename(), innerScript->lineno(), innerScript->getUseCount(), michael@0: (unsigned) bailoutKind); michael@0: michael@0: switch (bailoutKind) { michael@0: case Bailout_Normal: michael@0: // Do nothing. michael@0: break; michael@0: case Bailout_ArgumentCheck: michael@0: // Do nothing, bailout will resume before the argument monitor ICs. michael@0: break; michael@0: case Bailout_BoundsCheck: michael@0: if (!HandleBoundsCheckFailure(cx, outerScript, innerScript)) michael@0: return false; michael@0: break; michael@0: case Bailout_ShapeGuard: michael@0: if (!HandleShapeGuardFailure(cx, outerScript, innerScript)) michael@0: return false; michael@0: break; michael@0: case Bailout_BaselineInfo: michael@0: if (!HandleBaselineInfoBailout(cx, outerScript, innerScript)) michael@0: return false; michael@0: break; michael@0: case Bailout_IonExceptionDebugMode: michael@0: // Return false to resume in HandleException with reconstructed michael@0: // baseline frame. michael@0: return false; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("Unknown bailout kind!"); michael@0: } michael@0: michael@0: if (!CheckFrequentBailouts(cx, outerScript)) michael@0: return false; michael@0: michael@0: return true; michael@0: }