| /* -*- 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 "BaselineCompiler.h" |
| #include "BaselineIC.h" |
| #include "BaselineJIT.h" |
| #include "CompileInfo.h" |
| #include "IonSpewer.h" |
| #include "IonFrames-inl.h" |
| |
| #include "vm/Stack-inl.h" |
| |
| #include "jsopcodeinlines.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_; |
| JS_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 accomodate 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 |
| { |
| IonBailoutIterator &iter_; |
| IonJSFrameLayout *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(IonBailoutIterator &iter, size_t initialSize) |
| : iter_(iter), |
| frame_(static_cast<IonJSFrameLayout*>(iter.current())), |
| bufferTotal_(initialSize), |
| bufferAvail_(0), |
| bufferUsed_(0), |
| buffer_(NULL), |
| header_(NULL), |
| framePushed_(0) |
| { |
| JS_ASSERT(bufferTotal_ >= HeaderSize()); |
| } |
| |
| ~BaselineStackBuilder() { |
| if (buffer_) |
| js_free(buffer_); |
| } |
| |
| bool init() { |
| JS_ASSERT(!buffer_); |
| JS_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 = NULL; |
| header_->resumeAddr = NULL; |
| header_->monitorStub = NULL; |
| header_->numFrames = 0; |
| return true; |
| } |
| |
| bool enlarge() { |
| JS_ASSERT(buffer_ != NULL); |
| size_t newSize = bufferTotal_ * 2; |
| if (newSize == 0) |
| return false; |
| 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() { |
| JS_ASSERT(header_ == reinterpret_cast<BaselineBailoutInfo *>(buffer_)); |
| return header_; |
| } |
| |
| BaselineBailoutInfo *takeBuffer() { |
| JS_ASSERT(header_ == reinterpret_cast<BaselineBailoutInfo *>(buffer_)); |
| buffer_ = NULL; |
| return header_; |
| } |
| |
| void resetFramePushed() { |
| framePushed_ = 0; |
| } |
| |
| size_t framePushed() const { |
| return framePushed_; |
| } |
| |
| bool subtract(size_t size, const char *info=NULL) { |
| // 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) { |
| IonSpew(IonSpew_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) |
| IonSpew(IonSpew_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) { |
| IonSpew(IonSpew_BaselineBailouts, |
| " WRITE_WRD %p/%p %-15s %08x", |
| header_->copyStackBottom, virtualPointerAtStackOffset(0), info, w); |
| } else { |
| IonSpew(IonSpew_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) { |
| IonSpew(IonSpew_BaselineBailouts, |
| " WRITE_VAL %p/%p %-15s %016llx", |
| header_->copyStackBottom, virtualPointerAtStackOffset(0), info, |
| *((uint64_t *) &val)); |
| } |
| return true; |
| } |
| |
| Value popValue() { |
| JS_ASSERT(bufferUsed_ >= sizeof(Value)); |
| JS_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) { |
| JS_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: |
| JS_ASSERT(loc == PCMappingSlotInfo::SlotIgnore); |
| popValue(); |
| break; |
| } |
| } |
| |
| void setResumeFramePtr(void *resumeFramePtr) { |
| header_->resumeFramePtr = resumeFramePtr; |
| } |
| |
| void setResumeAddr(void *resumeAddr) { |
| header_->resumeAddr = resumeAddr; |
| } |
| |
| 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 IonJSFrameLayout *startFrame() { |
| return frame_; |
| } |
| |
| BufferPointer<IonJSFrameLayout> topFrameAddress() { |
| return pointerAtStackOffset<IonJSFrameLayout>(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<IonJSFrameLayout> topFrame = topFrameAddress(); |
| FrameType type = topFrame->prevType(); |
| |
| // For OptimizedJS 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 == IonFrame_OptimizedJS || type == IonFrame_Entry) |
| return NULL; |
| |
| // BaselineStub - Baseline calling into Ion. |
| // PrevFramePtr needs to point to the BaselineStubFrame's saved frame pointer. |
| // STACK_START_ADDR + IonJSFrameLayout::Size() + PREV_FRAME_SIZE |
| // - IonBaselineStubFrameLayout::reverseOffsetOfSavedFramePtr() |
| if (type == IonFrame_BaselineStub) { |
| size_t offset = IonJSFrameLayout::Size() + topFrame->prevFrameLocalSize() + |
| IonBaselineStubFrameLayout::reverseOffsetOfSavedFramePtr(); |
| return virtualPointerAtStackOffset(offset); |
| } |
| |
| JS_ASSERT(type == IonFrame_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 = IonJSFrameLayout::Size() + topFrame->prevFrameLocalSize(); |
| #if defined(JS_CPU_X86) |
| // On X86, the FramePointer is pushed as the first value in the Rectifier frame. |
| JS_ASSERT(BaselineFrameReg == FramePointer); |
| priorOffset -= sizeof(void *); |
| return virtualPointerAtStackOffset(priorOffset); |
| #elif defined(JS_CPU_X64) || defined(JS_CPU_ARM) || defined(JS_CPU_MIPS) |
| // On X64, ARM, and MIPS, the frame pointer save location depends on |
| // the caller of the the rectifier frame. |
| BufferPointer<IonRectifierFrameLayout> priorFrame = |
| pointerAtStackOffset<IonRectifierFrameLayout>(priorOffset); |
| FrameType priorType = priorFrame->prevType(); |
| JS_ASSERT(priorType == IonFrame_OptimizedJS || priorType == IonFrame_BaselineStub); |
| |
| // If the frame preceding the rectifier is an OptimizedJS frame, then once again |
| // the frame pointer does not matter. |
| if (priorType == IonFrame_OptimizedJS) |
| return NULL; |
| |
| // Otherwise, the frame preceding the rectifier is a BaselineStub frame. |
| // let X = STACK_START_ADDR + IonJSFrameLayout::Size() + PREV_FRAME_SIZE |
| // X + IonRectifierFrameLayout::Size() |
| // + ((IonRectifierFrameLayout *) X)->prevFrameLocalSize() |
| // - BaselineStubFrameLayout::reverseOffsetOfSavedFramePtr() |
| size_t extraOffset = IonRectifierFrameLayout::Size() + priorFrame->prevFrameLocalSize() + |
| IonBaselineStubFrameLayout::reverseOffsetOfSavedFramePtr(); |
| return virtualPointerAtStackOffset(priorOffset + extraOffset); |
| #else |
| # error "Bad architecture!" |
| #endif |
| } |
| }; |
| |
| // 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 | |
| // | +---------------+ |
| // | | ArgA | |
| // | +---------------+ |
| // | | ... | |
| // +--< +---------------+ |
| // | | | Arg0 | |
| // | | +---------------+ |
| // | | | ThisV | |
| // | -- +---------------+ |
| // | | ActualArgC | |
| // | +---------------+ |
| // | | CalleeToken | |
| // | +---------------+ |
| // +------------| Descr(BLStub) | |
| // +---------------+ |
| // | ReturnAddr | <-- return into ICCall_Scripted IC |
| // -- +===============+ --- IF CALLEE FORMAL ARGS > ActualArgC |
| // | | 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) |
| { |
| uint32_t exprStackSlots = iter.slots() - (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 |
| // +===============+ |
| |
| IonSpew(IonSpew_BaselineBailouts, " Unpacking %s:%d", script->filename(), script->lineno); |
| IonSpew(IonSpew_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); |
| |
| // Initialize BaselineFrame::frameSize |
| uint32_t frameSize = BaselineFrame::Size() + BaselineFrame::FramePointerOffset + |
| (sizeof(Value) * (script->nfixed + exprStackSlots)); |
| IonSpew(IonSpew_BaselineBailouts, " FrameSize=%d", (int) frameSize); |
| blFrame->setFrameSize(frameSize); |
| |
| uint32_t flags = 0; |
| |
| // If SPS Profiler is enabled, mark the frame as having pushed an SPS entry. |
| // This may be wrong for the last frame of ArgumentCheck bailout, but |
| // that will be fixed later. |
| if (cx->runtime()->spsProfiler.enabled() && ionScript->hasSPSInstrumentation()) { |
| IonSpew(IonSpew_BaselineBailouts, " Setting SPS flag on frame!"); |
| flags |= BaselineFrame::HAS_PUSHED_SPS_FRAME; |
| } |
| |
| // Initialize BaselineFrame's scopeChain and argsObj |
| JSObject *scopeChain = NULL; |
| ArgumentsObject *argsObj = NULL; |
| 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|. |
| IonSpew(IonSpew_BaselineBailouts, " Bailout_ArgumentCheck! (no valid scopeChain)"); |
| iter.skip(); |
| |
| // Scripts with |argumentsHasVarBinding| have an extra slot. |
| if (script->argumentsHasVarBinding()) { |
| IonSpew(IonSpew_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->isHeavyweight()) |
| flags |= BaselineFrame::HAS_CALL_OBJ; |
| } else { |
| JS_ASSERT(v.isUndefined()); |
| |
| // 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 NULL and enter baseline code before the |
| // prologue. |
| if (iter.pcOffset() != 0 || iter.resumeAfter()) |
| scopeChain = fun->environment(); |
| } else { |
| // For global, compile-and-go scripts the scope chain is the |
| // script's global (Ion does not compile non-compile-and-go |
| // scripts). 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. |
| JS_ASSERT(!script->isForEval()); |
| JS_ASSERT(script->compileAndGo); |
| scopeChain = &(script->global()); |
| } |
| } |
| |
| // If script maybe has an arguments object, the second slot will hold it. |
| if (script->argumentsHasVarBinding()) { |
| v = iter.read(); |
| JS_ASSERT(v.isObject() || v.isUndefined()); |
| if (v.isObject()) |
| argsObj = &v.toObject().as<ArgumentsObject>(); |
| } |
| } |
| IonSpew(IonSpew_BaselineBailouts, " ScopeChain=%p", scopeChain); |
| blFrame->setScopeChain(scopeChain); |
| |
| // Do not need to initialize scratchValue or returnValue fields in BaselineFrame. |
| |
| blFrame->setFlags(flags); |
| |
| // initArgsObjUnchecked modifies the frame's flags, so call it after setFlags. |
| if (argsObj) |
| blFrame->initArgsObjUnchecked(*argsObj); |
| |
| // Ion doesn't compile code with try/catch, so the block object will always be |
| // null. |
| blFrame->setBlockChainNull(); |
| |
| if (fun) { |
| // The unpacked thisv and arguments should overwrite the pushed args present |
| // in the calling frame. |
| Value thisv = iter.read(); |
| IonSpew(IonSpew_BaselineBailouts, " Is function!"); |
| IonSpew(IonSpew_BaselineBailouts, " thisv=%016llx", *((uint64_t *) &thisv)); |
| |
| size_t thisvOffset = builder.framePushed() + IonJSFrameLayout::offsetOfThis(); |
| *builder.valuePointerAtStackOffset(thisvOffset) = thisv; |
| |
| JS_ASSERT(iter.slots() >= CountArgSlots(script, fun)); |
| IonSpew(IonSpew_BaselineBailouts, " frame slots %u, nargs %u, nfixed %u", |
| iter.slots(), 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. |
| JS_ASSERT(startFrameFormals.empty()); |
| if (!startFrameFormals.resize(fun->nargs)) |
| return false; |
| } |
| |
| for (uint32_t i = 0; i < fun->nargs; i++) { |
| Value arg = iter.read(); |
| IonSpew(IonSpew_BaselineBailouts, " arg %d = %016llx", |
| (int) i, *((uint64_t *) &arg)); |
| if (callerPC) { |
| size_t argOffset = builder.framePushed() + IonJSFrameLayout::offsetOfActualArg(i); |
| *builder.valuePointerAtStackOffset(argOffset) = arg; |
| } else { |
| startFrameFormals[i] = arg; |
| } |
| } |
| } |
| |
| for (uint32_t i = 0; i < script->nfixed; i++) { |
| Value slot = iter.read(); |
| if (!builder.writeValue(slot, "FixedValue")) |
| return false; |
| } |
| |
| // Get the PC |
| jsbytecode *pc = script->code + iter.pcOffset(); |
| JSOp op = JSOp(*pc); |
| bool resumeAfter = iter.resumeAfter(); |
| |
| // Fixup inlined JSOP_FUNCALL and JSOP_FUNAPPLY on the caller side. |
| // On the caller side this must represent like the function wasn't inlined. |
| uint32_t pushedSlots = 0; |
| AutoValueVector funapplyargs(cx); |
| if (iter.moreFrames() && |
| (op == JSOP_FUNCALL || op == JSOP_FUNAPPLY)) |
| { |
| uint32_t inlined_args = 0; |
| if (op == JSOP_FUNCALL) |
| inlined_args = 2 + GET_ARGC(pc) - 1; |
| else |
| inlined_args = 2 + blFrame->numActualArgs(); |
| |
| JS_ASSERT(exprStackSlots >= inlined_args); |
| pushedSlots = exprStackSlots - inlined_args; |
| |
| IonSpew(IonSpew_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. |
| IonSpew(IonSpew_BaselineBailouts, " pushing undefined to fixup funcall"); |
| if (!builder.writeValue(UndefinedValue(), "StackValue")) |
| return false; |
| } |
| |
| if (op == JSOP_FUNAPPLY) { |
| // 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. |
| IonSpew(IonSpew_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 (!funapplyargs.resize(inlined_args)) |
| return false; |
| for (uint32_t i = 0; i < inlined_args; i++) |
| funapplyargs[i] = iter.read(); |
| pushedSlots = exprStackSlots; |
| } |
| } |
| |
| IonSpew(IonSpew_BaselineBailouts, " pushing %u expression stack slots", |
| exprStackSlots - pushedSlots); |
| for (uint32_t i = pushedSlots; i < exprStackSlots; i++) { |
| Value v; |
| |
| // 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. |
| if (!iter.moreFrames() && i == exprStackSlots - 1 && |
| cx->runtime()->hasIonReturnOverride()) |
| { |
| JS_ASSERT(invalidate); |
| iter.skip(); |
| IonSpew(IonSpew_BaselineBailouts, " [Return Override]"); |
| v = cx->runtime()->takeIonReturnOverride(); |
| } else { |
| v = iter.read(); |
| } |
| if (!builder.writeValue(v, "StackValue")) |
| return false; |
| } |
| |
| size_t endOfBaselineJSFrameStack = builder.framePushed(); |
| |
| // 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. |
| if (!resumeAfter) { |
| while (true) { |
| op = JSOp(*pc); |
| if (op == JSOP_GOTO) |
| pc += GET_JUMP_OFFSET(pc); |
| else if (op == JSOP_LOOPENTRY || op == JSOP_NOP || op == JSOP_LOOPHEAD) |
| pc = GetNextPc(pc); |
| else |
| break; |
| } |
| } |
| |
| uint32_t pcOff = pc - script->code; |
| bool isCall = js_CodeSpec[op].format & JOF_INVOKE; |
| BaselineScript *baselineScript = script->baselineScript(); |
| |
| #ifdef DEBUG |
| uint32_t expectedDepth = js_ReconstructStackDepth(cx, script, |
| resumeAfter ? GetNextPc(pc) : pc); |
| 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. |
| JS_ASSERT(expectedDepth - exprStackSlots <= 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. |
| JS_ASSERT(exprStackSlots == expectedDepth); |
| } |
| } |
| |
| IonSpew(IonSpew_BaselineBailouts, " Resuming %s pc offset %d (op %s) (line %d) of %s:%d", |
| resumeAfter ? "after" : "at", (int) pcOff, js_CodeName[op], |
| PCToLineNumber(script, pc), script->filename(), (int) script->lineno); |
| IonSpew(IonSpew_BaselineBailouts, " Bailout kind: %s", |
| BailoutKindString(bailoutKind)); |
| #endif |
| |
| // If this was the last inline frame, then unpacking is almost done. |
| if (!iter.moreFrames()) { |
| // Last frame, so PC for call to next frame is set to NULL. |
| *callPC = NULL; |
| |
| // 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 && (js_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.setResumeFramePtr(prevFramePtr); |
| |
| if (enterMonitorChain) { |
| ICEntry &icEntry = baselineScript->icEntryFromPCOffset(pcOff); |
| ICFallbackStub *fallbackStub = icEntry.firstStub()->getChainFallback(); |
| JS_ASSERT(fallbackStub->isMonitoredFallback()); |
| IonSpew(IonSpew_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 |
| IonSpew(IonSpew_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); |
| IonSpew(IonSpew_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"); |
| |
| frameSize += (numCallArgs + 2) * sizeof(Value); |
| blFrame->setFrameSize(frameSize); |
| IonSpew(IonSpew_BaselineBailouts, " Adjusted framesize += %d: %d", |
| (int) ((numCallArgs + 2) * 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); |
| IonSpew(IonSpew_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. |
| PCMappingSlotInfo slotInfo; |
| uint8_t *nativeCodeForPC = baselineScript->nativeCodeForPC(script, pc, &slotInfo); |
| unsigned numUnsynced = slotInfo.numUnsynced(); |
| JS_ASSERT(numUnsynced <= 2); |
| PCMappingSlotInfo::SlotLocation loc1, loc2; |
| if (numUnsynced > 0) { |
| loc1 = slotInfo.topSlotLocation(); |
| IonSpew(IonSpew_BaselineBailouts, " Popping top stack value into %d.", |
| (int) loc1); |
| builder.popValueInto(loc1); |
| } |
| if (numUnsynced > 1) { |
| loc2 = slotInfo.nextSlotLocation(); |
| IonSpew(IonSpew_BaselineBailouts, " Popping next stack value into %d.", |
| (int) loc2); |
| JS_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); |
| IonSpew(IonSpew_BaselineBailouts, " Adjusted framesize -= %d: %d", |
| int(sizeof(Value) * numUnsynced), int(frameSize)); |
| |
| // If scopeChain is NULL, then bailout is occurring during argument check. |
| // In this case, resume into the prologue. |
| uint8_t *opReturnAddr; |
| if (scopeChain == NULL) { |
| // Global and eval scripts expect the scope chain in R1, so only |
| // resume into the prologue for function scripts. |
| JS_ASSERT(fun); |
| JS_ASSERT(numUnsynced == 0); |
| opReturnAddr = baselineScript->prologueEntryAddr(); |
| IonSpew(IonSpew_BaselineBailouts, " Resuming into prologue."); |
| |
| // If bailing into prologue, HAS_PUSHED_SPS_FRAME should not be set on frame. |
| blFrame->unsetPushedSPSFrame(); |
| |
| // Additionally, if SPS is enabled, there are two corner cases to handle: |
| // 1. If resuming into the prologue, and innermost frame is an inlined frame, |
| // and bailout is because of argument check failure, then: |
| // Top SPS profiler entry would be for caller frame. |
| // Ion would not have set the PC index field on that frame |
| // (since this bailout happens before MFunctionBoundary). |
| // Make sure that's done now. |
| // 2. If resuming into the prologue, and the bailout is NOT because of an |
| // argument check, then: |
| // Top SPS profiler entry would be for callee frame. |
| // Ion would already have pushed an SPS entry for this frame. |
| // The pc for this entry would be set to NULL. |
| // Make sure it's set to script->pc. |
| if (cx->runtime()->spsProfiler.enabled()) { |
| if (caller && bailoutKind == Bailout_ArgumentCheck) { |
| IonSpew(IonSpew_BaselineBailouts, " Setting PCidx on innermost " |
| "inlined frame's parent's SPS entry (%s:%d) (pcIdx=%d)!", |
| caller->filename(), caller->lineno, callerPC - caller->code); |
| cx->runtime()->spsProfiler.updatePC(caller, callerPC); |
| } else if (bailoutKind != Bailout_ArgumentCheck) { |
| IonSpew(IonSpew_BaselineBailouts, |
| " Popping SPS entry for innermost inlined frame's SPS entry"); |
| cx->runtime()->spsProfiler.exit(cx, script, fun); |
| } |
| } |
| } else { |
| opReturnAddr = nativeCodeForPC; |
| } |
| builder.setResumeAddr(opReturnAddr); |
| IonSpew(IonSpew_BaselineBailouts, " Set resumeAddr=%p", opReturnAddr); |
| } |
| |
| return true; |
| } |
| |
| *callPC = pc; |
| |
| // Write out descriptor of BaselineJS frame. |
| size_t baselineFrameDescr = MakeFrameDescriptor((uint32_t) builder.framePushed(), |
| IonFrame_BaselineJS); |
| if (!builder.writeWord(baselineFrameDescr, "Descriptor")) |
| return false; |
| |
| // Calculate and write out return address. |
| // The icEntry in question MUST have a ICCall_Fallback as its fallback stub. |
| ICEntry &icEntry = baselineScript->icEntryFromPCOffset(pcOff); |
| JS_ASSERT(icEntry.firstStub()->getChainFallback()->isCall_Fallback()); |
| if (!builder.writePtr(baselineScript->returnAddressForIC(icEntry), "ReturnAddr")) |
| return false; |
| |
| // Build baseline stub frame: |
| // +===============+ |
| // | StubPtr | |
| // +---------------+ |
| // | FramePtr | |
| // +---------------+ |
| // | ArgA | |
| // +---------------+ |
| // | ... | |
| // +---------------+ |
| // | Arg0 | |
| // +---------------+ |
| // | ThisV | |
| // +---------------+ |
| // | ActualArgC | |
| // +---------------+ |
| // | CalleeToken | |
| // +---------------+ |
| // | Descr(BLStub) | |
| // +---------------+ |
| // | ReturnAddr | |
| // +===============+ |
| |
| IonSpew(IonSpew_BaselineBailouts, " [BASELINE-STUB FRAME]"); |
| |
| size_t startOfBaselineStubFrame = builder.framePushed(); |
| |
| // Write stub pointer. |
| JS_ASSERT(icEntry.fallbackStub()->isCall_Fallback()); |
| 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. |
| JS_ASSERT(isCall); |
| unsigned actualArgc = GET_ARGC(pc); |
| if (op == JSOP_FUNAPPLY) { |
| // For FUNAPPLY the arguments are not on the stack anymore, |
| // but they are copied in a vector and are written here. |
| actualArgc = blFrame->numActualArgs(); |
| |
| JS_ASSERT(actualArgc + 2 <= exprStackSlots); |
| JS_ASSERT(funapplyargs.length() == actualArgc + 2); |
| for (unsigned i = 0; i < actualArgc + 1; i++) { |
| size_t arg = funapplyargs.length() - (i + 1); |
| if (!builder.writeValue(funapplyargs[arg], "ArgVal")) |
| return false; |
| } |
| } else { |
| if (op == JSOP_FUNCALL) { |
| JS_ASSERT(actualArgc > 0); |
| actualArgc--; |
| } |
| |
| JS_ASSERT(actualArgc + 2 <= exprStackSlots); |
| for (unsigned i = 0; i < actualArgc + 1; i++) { |
| size_t argSlot = (script->nfixed + exprStackSlots) - (i + 1); |
| if (!builder.writeValue(*blFrame->valueSlot(argSlot), "ArgVal")) |
| return false; |
| } |
| } |
| |
| // 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, |
| IonFrame_BaselineStub); |
| |
| // Push actual argc |
| if (!builder.writeWord(actualArgc, "ActualArgc")) |
| return false; |
| |
| // Push callee token (must be a JS Function) |
| Value callee; |
| if (op == JSOP_FUNAPPLY) { |
| // The arguments of FUNAPPLY are not writen to the stack. |
| // So get the callee from the specially saved vector. |
| callee = funapplyargs[0]; |
| } else { |
| uint32_t calleeStackSlot = exprStackSlots - uint32_t(actualArgc + 2); |
| size_t calleeOffset = (builder.framePushed() - endOfBaselineJSFrameStack) |
| + ((exprStackSlots - (calleeStackSlot + 1)) * sizeof(Value)); |
| callee = *builder.valuePointerAtStackOffset(calleeOffset); |
| IonSpew(IonSpew_BaselineBailouts, " CalleeStackSlot=%d", (int) calleeStackSlot); |
| } |
| IonSpew(IonSpew_BaselineBailouts, " Callee = %016llx", *((uint64_t *) &callee)); |
| JS_ASSERT(callee.isObject() && callee.toObject().is<JSFunction>()); |
| JSFunction *calleeFun = &callee.toObject().as<JSFunction>(); |
| if (!builder.writePtr(CalleeToToken(calleeFun), "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 = cx->compartment()->ionCompartment()->baselineCallReturnAddr(); |
| JS_ASSERT(baselineCallReturnAddr); |
| if (!builder.writePtr(baselineCallReturnAddr, "ReturnAddr")) |
| return false; |
| |
| // 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. |
| // +===============+ |
| // | UndefinedU | |
| // +---------------+ |
| // | ... | |
| // +---------------+ |
| // | Undefined0 | |
| // +---------------+ |
| // | ArgA | |
| // +---------------+ |
| // | ... | |
| // +---------------+ |
| // | Arg0 | |
| // +---------------+ |
| // | ThisV | |
| // +---------------+ |
| // | ActualArgC | |
| // +---------------+ |
| // | CalleeToken | |
| // +---------------+ |
| // | Descr(Rect) | |
| // +---------------+ |
| // | ReturnAddr | |
| // +===============+ |
| |
| IonSpew(IonSpew_BaselineBailouts, " [RECTIFIER FRAME]"); |
| |
| size_t startOfRectifierFrame = builder.framePushed(); |
| |
| // On x86-only, the frame pointer is saved again in the rectifier frame. |
| #if defined(JS_CPU_X86) |
| if (!builder.writePtr(prevFramePtr, "PrevFramePtr-X86Only")) |
| return false; |
| #endif |
| |
| // 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); |
| IonSpew(IonSpew_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, |
| IonFrame_Rectifier); |
| |
| // Push actualArgc |
| if (!builder.writeWord(actualArgc, "ActualArgc")) |
| return false; |
| |
| // Push calleeToken again. |
| if (!builder.writePtr(CalleeToToken(calleeFun), "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->compartment()->ionCompartment()->getArgumentsRectifierReturnAddr(); |
| JS_ASSERT(rectReturnAddr); |
| if (!builder.writePtr(rectReturnAddr, "ReturnAddr")) |
| return false; |
| |
| return true; |
| } |
| |
| uint32_t |
| jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, IonBailoutIterator &iter, |
| bool invalidate, BaselineBailoutInfo **bailoutInfo) |
| { |
| JS_ASSERT(bailoutInfo != NULL); |
| JS_ASSERT(*bailoutInfo == NULL); |
| |
| // The caller of the top frame must be one of the following: |
| // OptimizedJS - Ion calling into Ion. |
| // BaselineStub - Baseline calling into Ion. |
| // Entry - Interpreter or other calling into Ion. |
| // Rectifier - Arguments rectifier calling into Ion. |
| JS_ASSERT(iter.isOptimizedJS()); |
| FrameType prevFrameType = iter.prevType(); |
| JS_ASSERT(prevFrameType == IonFrame_OptimizedJS || |
| prevFrameType == IonFrame_BaselineStub || |
| prevFrameType == IonFrame_Entry || |
| prevFrameType == IonFrame_Rectifier); |
| |
| // All incoming frames are going to look like this: |
| // |
| // +---------------+ |
| // | ... | |
| // +---------------+ |
| // | Args | |
| // | ... | |
| // +---------------+ |
| // | ThisV | |
| // +---------------+ |
| // | ActualArgC | |
| // +---------------+ |
| // | CalleeToken | |
| // +---------------+ |
| // | Descriptor | |
| // +---------------+ |
| // | ReturnAddr | |
| // +---------------+ |
| // | ||||| | <---- Overwrite starting here. |
| // | ||||| | |
| // | ||||| | |
| // +---------------+ |
| |
| IonSpew(IonSpew_BaselineBailouts, "Bailing to baseline %s:%u (IonScript=%p) (FrameType=%d)", |
| iter.script()->filename(), iter.script()->lineno, (void *) iter.ionScript(), |
| (int) prevFrameType); |
| IonSpew(IonSpew_BaselineBailouts, " Reading from snapshot offset %u size %u", |
| iter.snapshotOffset(), iter.ionScript()->snapshotsSize()); |
| |
| iter.ionScript()->incNumBailouts(); |
| iter.script()->updateBaselineOrIonRaw(); |
| |
| // Allocate buffer to hold stack replacement data. |
| BaselineStackBuilder builder(iter, 1024); |
| if (!builder.init()) |
| return BAILOUT_RETURN_FATAL_ERROR; |
| IonSpew(IonSpew_BaselineBailouts, " Incoming frame ptr = %p", builder.startFrame()); |
| |
| SnapshotIterator snapIter(iter); |
| |
| RootedFunction callee(cx, iter.maybeCallee()); |
| RootedScript scr(cx, iter.script()); |
| if (callee) { |
| IonSpew(IonSpew_BaselineBailouts, " Callee function (%s:%u)", |
| scr->filename(), scr->lineno); |
| } else { |
| IonSpew(IonSpew_BaselineBailouts, " No callee!"); |
| } |
| |
| if (iter.isConstructing()) |
| IonSpew(IonSpew_BaselineBailouts, " Constructing!"); |
| else |
| IonSpew(IonSpew_BaselineBailouts, " Not constructing!"); |
| |
| IonSpew(IonSpew_BaselineBailouts, " Restoring frames:"); |
| int frameNo = 0; |
| |
| // Reconstruct baseline frames using the builder. |
| RootedScript caller(cx); |
| jsbytecode *callerPC = NULL; |
| RootedFunction fun(cx, callee); |
| AutoValueVector startFrameFormals(cx); |
| while (true) { |
| IonSpew(IonSpew_BaselineBailouts, " FrameNo %d", frameNo); |
| jsbytecode *callPC = NULL; |
| RootedFunction nextCallee(cx, NULL); |
| if (!InitFromBailout(cx, caller, callerPC, fun, scr, iter.ionScript(), |
| snapIter, invalidate, builder, startFrameFormals, |
| &nextCallee, &callPC)) |
| { |
| return BAILOUT_RETURN_FATAL_ERROR; |
| } |
| |
| if (!snapIter.moreFrames()) { |
| JS_ASSERT(!callPC); |
| break; |
| } |
| |
| JS_ASSERT(nextCallee); |
| JS_ASSERT(callPC); |
| caller = scr; |
| callerPC = callPC; |
| fun = nextCallee; |
| scr = fun->existingScriptForInlinedFunction(); |
| snapIter.nextFrame(); |
| |
| frameNo++; |
| } |
| IonSpew(IonSpew_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()); |
| } |
| |
| // Take the reconstructed baseline stack so it doesn't get freed when builder destructs. |
| BaselineBailoutInfo *info = builder.takeBuffer(); |
| info->numFrames = frameNo + 1; |
| |
| // Do stack check. |
| bool overRecursed = false; |
| JS_CHECK_RECURSION_WITH_EXTRA_DONT_REPORT(cx, info->copyStackTop - info->copyStackBottom, |
| overRecursed = true); |
| if (overRecursed) |
| return BAILOUT_RETURN_OVERRECURSED; |
| |
| info->bailoutKind = bailoutKind; |
| *bailoutInfo = info; |
| return BAILOUT_RETURN_OK; |
| } |
| |
| static bool |
| HandleBoundsCheckFailure(JSContext *cx, HandleScript outerScript, HandleScript innerScript) |
| { |
| IonSpew(IonSpew_Bailouts, "Bounds check failure %s:%d, inlined into %s:%d", |
| innerScript->filename(), innerScript->lineno, |
| outerScript->filename(), outerScript->lineno); |
| |
| JS_ASSERT(!outerScript->ionScript()->invalidated()); |
| |
| // 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. |
| if (!outerScript->failedBoundsCheck) { |
| outerScript->failedBoundsCheck = true; |
| } |
| IonSpew(IonSpew_BaselineBailouts, "Invalidating due to bounds check failure"); |
| return Invalidate(cx, outerScript); |
| } |
| |
| static bool |
| HandleShapeGuardFailure(JSContext *cx, HandleScript outerScript, HandleScript innerScript) |
| { |
| IonSpew(IonSpew_Bailouts, "Shape guard failure %s:%d, inlined into %s:%d", |
| innerScript->filename(), innerScript->lineno, |
| outerScript->filename(), outerScript->lineno); |
| |
| JS_ASSERT(!outerScript->ionScript()->invalidated()); |
| |
| // 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->failedShapeGuard = true; |
| IonSpew(IonSpew_BaselineBailouts, "Invalidating due to shape guard failure"); |
| return Invalidate(cx, outerScript); |
| } |
| |
| static bool |
| HandleCachedShapeGuardFailure(JSContext *cx, HandleScript outerScript, HandleScript innerScript) |
| { |
| IonSpew(IonSpew_Bailouts, "Cached shape guard failure %s:%d, inlined into %s:%d", |
| innerScript->filename(), innerScript->lineno, |
| outerScript->filename(), outerScript->lineno); |
| |
| JS_ASSERT(!outerScript->ionScript()->invalidated()); |
| |
| outerScript->failedShapeGuard = true; |
| |
| // No need to purge baseline ICs. Baseline will do one of two things: add a new |
| // optimized stub (preventing monomorphic IC caching), or set a flag indicating that |
| // an unoptimizable access was made, also preventing mono IC caching. |
| |
| IonSpew(IonSpew_BaselineBailouts, "Invalidating due to cached shape guard failure"); |
| |
| return Invalidate(cx, outerScript); |
| } |
| |
| 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 = GetIonContext()->cx; |
| js::gc::AutoSuppressGC suppressGC(cx); |
| |
| IonSpew(IonSpew_BaselineBailouts, " Done restoring frames"); |
| |
| // Check that we can get the current script's PC. |
| #ifdef DEBUG |
| jsbytecode *pc; |
| cx->currentScript(&pc); |
| IonSpew(IonSpew_BaselineBailouts, " Got pc=%p", pc); |
| #endif |
| |
| uint32_t numFrames = bailoutInfo->numFrames; |
| JS_ASSERT(numFrames > 0); |
| BailoutKind bailoutKind = bailoutInfo->bailoutKind; |
| |
| // Free the bailout buffer. |
| js_free(bailoutInfo); |
| bailoutInfo = NULL; |
| |
| // Ensure the frame has a call object if it needs one. If the scope chain |
| // is NULL, we will enter baseline code at the prologue so no need to do |
| // anything in that case. |
| BaselineFrame *topFrame = GetTopBaselineFrame(cx); |
| 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, NULL); |
| RootedScript outerScript(cx, NULL); |
| |
| JS_ASSERT(cx->currentlyRunningInJit()); |
| IonFrameIterator iter(cx->mainThread().ionTop); |
| |
| uint32_t frameno = 0; |
| while (frameno < numFrames) { |
| JS_ASSERT(!iter.isOptimizedJS()); |
| |
| if (iter.isBaselineJS()) { |
| BaselineFrame *frame = iter.baselineFrame(); |
| |
| // 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(); |
| |
| frameno++; |
| } |
| |
| ++iter; |
| } |
| |
| JS_ASSERT(innerScript); |
| JS_ASSERT(outerScript); |
| IonSpew(IonSpew_BaselineBailouts, |
| " Restored outerScript=(%s:%u,%u) innerScript=(%s:%u,%u) (bailoutKind=%u)", |
| outerScript->filename(), outerScript->lineno, outerScript->getUseCount(), |
| innerScript->filename(), innerScript->lineno, innerScript->getUseCount(), |
| (unsigned) bailoutKind); |
| |
| switch (bailoutKind) { |
| case Bailout_Normal: |
| // Do nothing. |
| break; |
| case Bailout_ArgumentCheck: |
| case Bailout_TypeBarrier: |
| case Bailout_Monitor: |
| // Reflow types. But in baseline, this will happen automatically because |
| // for any monitored op (or for argument checks), bailout will resume into |
| // the monitoring IC which will handle the type updates. |
| break; |
| case Bailout_BoundsCheck: |
| if (!HandleBoundsCheckFailure(cx, outerScript, innerScript)) |
| return false; |
| break; |
| case Bailout_ShapeGuard: |
| if (!HandleShapeGuardFailure(cx, outerScript, innerScript)) |
| return false; |
| break; |
| case Bailout_CachedShapeGuard: |
| if (!HandleCachedShapeGuardFailure(cx, outerScript, innerScript)) |
| return false; |
| break; |
| default: |
| JS_NOT_REACHED("Unknown bailout kind!"); |
| } |
| |
| if (!CheckFrequentBailouts(cx, outerScript)) |
| return false; |
| |
| return true; |
| } |