| /* |
| * Copyright (C) 2015-2019 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #pragma once |
| |
| #if ENABLE(JIT) |
| |
| #include "CachedRecovery.h" |
| #include "CallFrameShuffleData.h" |
| #include "MacroAssembler.h" |
| #include "RegisterSet.h" |
| #include <wtf/Vector.h> |
| |
| namespace JSC { |
| |
| class CallFrameShuffler { |
| WTF_MAKE_FAST_ALLOCATED; |
| public: |
| CallFrameShuffler(CCallHelpers&, const CallFrameShuffleData&); |
| |
| void dump(PrintStream&) const; |
| |
| // Any register that has been locked or acquired must be released |
| // before calling prepareForTailCall() or prepareForSlowPath(). |
| void lockGPR(GPRReg gpr) |
| { |
| ASSERT(!m_lockedRegisters.get(gpr)); |
| m_lockedRegisters.set(gpr); |
| if (verbose) |
| dataLog(" * Locking ", gpr, "\n"); |
| } |
| |
| GPRReg acquireGPR() |
| { |
| ensureGPR(); |
| GPRReg gpr { getFreeGPR() }; |
| ASSERT(!m_registers[gpr]); |
| lockGPR(gpr); |
| return gpr; |
| } |
| |
| void releaseGPR(GPRReg gpr) |
| { |
| if (verbose) { |
| if (m_lockedRegisters.get(gpr)) |
| dataLog(" * Releasing ", gpr, "\n"); |
| else |
| dataLog(" * ", gpr, " was not locked\n"); |
| } |
| m_lockedRegisters.clear(gpr); |
| } |
| |
| void restoreGPR(GPRReg gpr) |
| { |
| if (!m_newRegisters[gpr]) |
| return; |
| |
| ensureGPR(); |
| #if USE(JSVALUE32_64) |
| GPRReg tempGPR { getFreeGPR() }; |
| lockGPR(tempGPR); |
| ensureGPR(); |
| releaseGPR(tempGPR); |
| #endif |
| emitDisplace(*m_newRegisters[gpr]); |
| } |
| |
| // You can only take a snapshot if the recovery has not started |
| // yet. The only operations that are valid before taking a |
| // snapshot are lockGPR(), acquireGPR() and releaseGPR(). |
| // |
| // Locking status is *NOT* preserved by the snapshot: it only |
| // contains information about where the |
| // arguments/callee/callee-save registers are by taking into |
| // account any spilling that acquireGPR() could have done. |
| CallFrameShuffleData snapshot() const |
| { |
| ASSERT(isUndecided()); |
| |
| CallFrameShuffleData data; |
| data.numLocals = numLocals(); |
| data.numPassedArgs = m_numPassedArgs; |
| data.callee = getNew(VirtualRegister { CallFrameSlot::callee })->recovery(); |
| data.args.resize(argCount()); |
| for (size_t i = 0; i < argCount(); ++i) |
| data.args[i] = getNew(virtualRegisterForArgument(i))->recovery(); |
| for (Reg reg = Reg::first(); reg <= Reg::last(); reg = reg.next()) { |
| CachedRecovery* cachedRecovery { m_newRegisters[reg] }; |
| if (!cachedRecovery) |
| continue; |
| |
| #if USE(JSVALUE64) |
| data.registers[reg] = cachedRecovery->recovery(); |
| #else |
| RELEASE_ASSERT_NOT_REACHED(); |
| #endif |
| } |
| return data; |
| } |
| |
| // Ask the shuffler to put the callee into some registers once the |
| // shuffling is done. You should call this before any of the |
| // prepare() methods, and must not take a snapshot afterwards, as |
| // this would crash 32bits platforms. |
| void setCalleeJSValueRegs(JSValueRegs jsValueRegs) |
| { |
| ASSERT(isUndecided()); |
| ASSERT(!getNew(jsValueRegs)); |
| CachedRecovery* cachedRecovery { getNew(CallFrameSlot::callee) }; |
| ASSERT(cachedRecovery); |
| addNew(jsValueRegs, cachedRecovery->recovery()); |
| } |
| |
| // Ask the suhffler to assume the callee has already be checked to |
| // be a cell. This is a no-op on 64bit platforms, but allows to |
| // free up a GPR on 32bit platforms. |
| // You obviously must have ensured that this is the case before |
| // running any of the prepare methods. |
| void assumeCalleeIsCell() |
| { |
| #if USE(JSVALUE32_64) |
| CachedRecovery& calleeCachedRecovery = *getNew(CallFrameSlot::callee); |
| switch (calleeCachedRecovery.recovery().technique()) { |
| case InPair: |
| updateRecovery( |
| calleeCachedRecovery, |
| ValueRecovery::inGPR( |
| calleeCachedRecovery.recovery().payloadGPR(), |
| DataFormatCell)); |
| break; |
| case DisplacedInJSStack: |
| updateRecovery( |
| calleeCachedRecovery, |
| ValueRecovery::displacedInJSStack( |
| calleeCachedRecovery.recovery().virtualRegister(), |
| DataFormatCell)); |
| break; |
| case InFPR: |
| case UnboxedCellInGPR: |
| case CellDisplacedInJSStack: |
| break; |
| case Constant: |
| ASSERT(calleeCachedRecovery.recovery().constant().isCell()); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| #endif |
| } |
| |
| // This will emit code to build the new frame over the old one. |
| void prepareForTailCall(); |
| |
| // This will emit code to build the new frame as if performing a |
| // regular call. However, the callee save registers will be |
| // restored, and any locals (not the header or arguments) of the |
| // current frame can be overwritten. |
| // |
| // A frame built using prepareForSlowPath() should be used either |
| // to throw an exception in, or destroyed using |
| // CCallHelpers::prepareForTailCallSlow() followed by a tail call. |
| void prepareForSlowPath(); |
| |
| private: |
| static constexpr bool verbose = false; |
| |
| CCallHelpers& m_jit; |
| |
| void prepareAny(); |
| |
| void spill(CachedRecovery&); |
| |
| // "box" is arguably a bad name here. The meaning is that after |
| // calling emitBox(), your ensure that subsequently calling |
| // emitStore() will be able to store the value without additional |
| // transformation. In particular, this is a no-op for constants, |
| // and is a complete no-op on 32bits since any unboxed value can |
| // still be stored by storing the payload and a statically known |
| // tag. |
| void emitBox(CachedRecovery&); |
| |
| bool canBox(CachedRecovery& cachedRecovery) |
| { |
| if (cachedRecovery.boxingRequiresGPR() && getFreeGPR() == InvalidGPRReg) |
| return false; |
| |
| if (cachedRecovery.boxingRequiresFPR() && getFreeFPR() == InvalidFPRReg) |
| return false; |
| |
| return true; |
| } |
| |
| void ensureBox(CachedRecovery& cachedRecovery) |
| { |
| if (canBox(cachedRecovery)) |
| return; |
| |
| if (cachedRecovery.boxingRequiresGPR()) |
| ensureGPR(); |
| |
| if (cachedRecovery.boxingRequiresFPR()) |
| ensureFPR(); |
| } |
| |
| void emitLoad(CachedRecovery&); |
| |
| bool canLoad(CachedRecovery&); |
| |
| void ensureLoad(CachedRecovery& cachedRecovery) |
| { |
| if (canLoad(cachedRecovery)) |
| return; |
| |
| ASSERT(cachedRecovery.loadsIntoGPR() || cachedRecovery.loadsIntoFPR()); |
| |
| if (cachedRecovery.loadsIntoFPR()) { |
| if (cachedRecovery.loadsIntoGPR()) |
| ensureRegister(); |
| else |
| ensureFPR(); |
| } else |
| ensureGPR(); |
| } |
| |
| bool canLoadAndBox(CachedRecovery& cachedRecovery) |
| { |
| // We don't have interfering loads & boxes |
| ASSERT(!cachedRecovery.loadsIntoFPR() || !cachedRecovery.boxingRequiresFPR()); |
| ASSERT(!cachedRecovery.loadsIntoGPR() || !cachedRecovery.boxingRequiresGPR()); |
| |
| return canLoad(cachedRecovery) && canBox(cachedRecovery); |
| } |
| |
| DataFormat emitStore(CachedRecovery&, MacroAssembler::Address); |
| |
| void emitDisplace(CachedRecovery&); |
| |
| void emitDeltaCheck(); |
| |
| Bag<CachedRecovery> m_cachedRecoveries; |
| |
| void updateRecovery(CachedRecovery& cachedRecovery, ValueRecovery recovery) |
| { |
| clearCachedRecovery(cachedRecovery.recovery()); |
| cachedRecovery.setRecovery(recovery); |
| setCachedRecovery(recovery, &cachedRecovery); |
| } |
| |
| CachedRecovery* getCachedRecovery(ValueRecovery); |
| |
| CachedRecovery* setCachedRecovery(ValueRecovery, CachedRecovery*); |
| |
| void clearCachedRecovery(ValueRecovery recovery) |
| { |
| if (!recovery.isConstant()) |
| setCachedRecovery(recovery, nullptr); |
| } |
| |
| CachedRecovery* addCachedRecovery(ValueRecovery recovery) |
| { |
| if (recovery.isConstant()) |
| return m_cachedRecoveries.add(recovery); |
| CachedRecovery* cachedRecovery = getCachedRecovery(recovery); |
| if (!cachedRecovery) |
| return setCachedRecovery(recovery, m_cachedRecoveries.add(recovery)); |
| return cachedRecovery; |
| } |
| |
| // This is the current recoveries present in the old frame's |
| // slots. A null CachedRecovery means we can trash the current |
| // value as we don't care about it. |
| Vector<CachedRecovery*> m_oldFrame; |
| |
| int numLocals() const |
| { |
| return m_oldFrame.size() - CallerFrameAndPC::sizeInRegisters; |
| } |
| |
| CachedRecovery* getOld(VirtualRegister reg) const |
| { |
| return m_oldFrame[CallerFrameAndPC::sizeInRegisters - reg.offset() - 1]; |
| } |
| |
| void setOld(VirtualRegister reg, CachedRecovery* cachedRecovery) |
| { |
| m_oldFrame[CallerFrameAndPC::sizeInRegisters - reg.offset() - 1] = cachedRecovery; |
| } |
| |
| VirtualRegister firstOld() const |
| { |
| return VirtualRegister { static_cast<int>(-numLocals()) }; |
| } |
| |
| VirtualRegister lastOld() const |
| { |
| return VirtualRegister { CallerFrameAndPC::sizeInRegisters - 1 }; |
| } |
| |
| bool isValidOld(VirtualRegister reg) const |
| { |
| return reg >= firstOld() && reg <= lastOld(); |
| } |
| |
| bool m_didExtendFrame { false }; |
| |
| void extendFrameIfNeeded(); |
| |
| // This stores, for each slot in the new frame, information about |
| // the recovery for the value that should eventually go into that |
| // slot. |
| // |
| // Once the slot has been written, the corresponding entry in |
| // m_newFrame will be empty. |
| Vector<CachedRecovery*> m_newFrame; |
| |
| size_t argCount() const |
| { |
| return m_newFrame.size() - CallFrame::headerSizeInRegisters; |
| } |
| |
| CachedRecovery* getNew(VirtualRegister newRegister) const |
| { |
| return m_newFrame[newRegister.offset()]; |
| } |
| |
| void setNew(VirtualRegister newRegister, CachedRecovery* cachedRecovery) |
| { |
| m_newFrame[newRegister.offset()] = cachedRecovery; |
| } |
| |
| void addNew(VirtualRegister newRegister, ValueRecovery recovery) |
| { |
| CachedRecovery* cachedRecovery = addCachedRecovery(recovery); |
| cachedRecovery->addTarget(newRegister); |
| setNew(newRegister, cachedRecovery); |
| } |
| |
| VirtualRegister firstNew() const |
| { |
| return VirtualRegister { 0 }; |
| } |
| |
| VirtualRegister lastNew() const |
| { |
| return VirtualRegister { static_cast<int>(m_newFrame.size()) - 1 }; |
| } |
| |
| bool isValidNew(VirtualRegister reg) const |
| { |
| return reg >= firstNew() && reg <= lastNew(); |
| } |
| |
| |
| int m_alignedOldFrameSize; |
| int m_alignedNewFrameSize; |
| |
| // This is the distance, in slots, between the base of the new |
| // frame and the base of the old frame. It could be negative when |
| // preparing for a tail call to a function with smaller argument |
| // count. |
| // |
| // We will overwrite this appropriately for slow path calls, but |
| // we initialize it as if doing a fast path for the spills we |
| // could do while undecided (typically while calling acquireGPR() |
| // for a polymorphic call). |
| int m_frameDelta; |
| |
| VirtualRegister newAsOld(VirtualRegister reg) const |
| { |
| return reg - m_frameDelta; |
| } |
| |
| // This stores the set of locked registers, i.e. registers for |
| // which we have an implicit requirement that they are not changed. |
| // |
| // This will usually contains the link register on architectures |
| // that have one, any scratch register used by the macro assembler |
| // (e.g. r11 on X86_64), as well as any register that we use for |
| // addressing (see m_oldFrameBase and m_newFrameBase). |
| // |
| // We also use this to lock registers temporarily, for instance to |
| // ensure that we have at least 2 available registers for loading |
| // a pair on 32bits. |
| mutable RegisterSet m_lockedRegisters; |
| |
| // This stores the current recoveries present in registers. A null |
| // CachedRecovery means we can trash the current value as we don't |
| // care about it. |
| RegisterMap<CachedRecovery*> m_registers; |
| |
| #if USE(JSVALUE64) |
| mutable GPRReg m_numberTagRegister; |
| |
| bool tryAcquireNumberTagRegister(); |
| #endif |
| |
| // This stores, for each register, information about the recovery |
| // for the value that should eventually go into that register. The |
| // only registers that have a target recovery will be callee-save |
| // registers, as well as possibly one JSValueRegs for holding the |
| // callee. |
| // |
| // Once the correct value has been put into the registers, and |
| // contrary to what we do with m_newFrame, we keep the entry in |
| // m_newRegisters to simplify spilling. |
| RegisterMap<CachedRecovery*> m_newRegisters; |
| |
| template<typename CheckFunctor> |
| Reg getFreeRegister(const CheckFunctor& check) const |
| { |
| Reg nonTemp { }; |
| for (Reg reg = Reg::first(); reg <= Reg::last(); reg = reg.next()) { |
| if (m_lockedRegisters.get(reg)) |
| continue; |
| |
| if (!check(reg)) |
| continue; |
| |
| if (!m_registers[reg]) { |
| if (!m_newRegisters[reg]) |
| return reg; |
| if (!nonTemp) |
| nonTemp = reg; |
| } |
| } |
| |
| #if USE(JSVALUE64) |
| if (!nonTemp && m_numberTagRegister != InvalidGPRReg && check(Reg { m_numberTagRegister })) { |
| ASSERT(m_lockedRegisters.get(m_numberTagRegister)); |
| m_lockedRegisters.clear(m_numberTagRegister); |
| nonTemp = Reg { m_numberTagRegister }; |
| m_numberTagRegister = InvalidGPRReg; |
| } |
| #endif |
| return nonTemp; |
| } |
| |
| GPRReg getFreeTempGPR() const |
| { |
| Reg freeTempGPR { getFreeRegister([this] (Reg reg) { return reg.isGPR() && !m_newRegisters[reg]; }) }; |
| if (!freeTempGPR) |
| return InvalidGPRReg; |
| return freeTempGPR.gpr(); |
| } |
| |
| GPRReg getFreeGPR() const |
| { |
| Reg freeGPR { getFreeRegister([] (Reg reg) { return reg.isGPR(); }) }; |
| if (!freeGPR) |
| return InvalidGPRReg; |
| return freeGPR.gpr(); |
| } |
| |
| FPRReg getFreeFPR() const |
| { |
| Reg freeFPR { getFreeRegister([] (Reg reg) { return reg.isFPR(); }) }; |
| if (!freeFPR) |
| return InvalidFPRReg; |
| return freeFPR.fpr(); |
| } |
| |
| bool hasFreeRegister() const |
| { |
| return static_cast<bool>(getFreeRegister([] (Reg) { return true; })); |
| } |
| |
| // This frees up a register satisfying the check functor (this |
| // functor could theoretically have any kind of logic, but it must |
| // ensure that it will only return true for registers - spill |
| // assumes and asserts that it is passed a cachedRecovery stored in a |
| // register). |
| template<typename CheckFunctor> |
| void ensureRegister(const CheckFunctor& check) |
| { |
| // If we can spill a callee-save, that's best, because it will |
| // free up a register that would otherwise been taken for the |
| // longest amount of time. |
| // |
| // We could try to bias towards those that are not in their |
| // target registers yet, but the gain is probably super |
| // small. Unless you have a huge number of argument (at least |
| // around twice the number of available registers on your |
| // architecture), no spilling is going to take place anyways. |
| for (Reg reg = Reg::first(); reg <= Reg::last(); reg = reg.next()) { |
| if (m_lockedRegisters.get(reg)) |
| continue; |
| |
| CachedRecovery* cachedRecovery { m_newRegisters[reg] }; |
| if (!cachedRecovery) |
| continue; |
| |
| if (check(*cachedRecovery)) { |
| if (verbose) |
| dataLog(" ", cachedRecovery->recovery(), " looks like a good spill candidate\n"); |
| spill(*cachedRecovery); |
| return; |
| } |
| } |
| |
| // We use the cachedRecovery associated with the first new slot we |
| // can, because that is the one for which a write will be |
| // possible the latest, i.e. that is the one that we would |
| // have had to retain in registers for the longest. |
| for (VirtualRegister reg = firstNew(); reg <= lastNew(); reg += 1) { |
| CachedRecovery* cachedRecovery { getNew(reg) }; |
| if (!cachedRecovery) |
| continue; |
| |
| if (check(*cachedRecovery)) { |
| spill(*cachedRecovery); |
| return; |
| } |
| } |
| |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| void ensureRegister() |
| { |
| if (hasFreeRegister()) |
| return; |
| |
| if (verbose) |
| dataLog(" Finding a register to spill\n"); |
| ensureRegister( |
| [this] (const CachedRecovery& cachedRecovery) { |
| if (cachedRecovery.recovery().isInGPR()) |
| return !m_lockedRegisters.get(cachedRecovery.recovery().gpr()); |
| if (cachedRecovery.recovery().isInFPR()) |
| return !m_lockedRegisters.get(cachedRecovery.recovery().fpr()); |
| #if USE(JSVALUE32_64) |
| if (cachedRecovery.recovery().technique() == InPair) { |
| return !m_lockedRegisters.get(cachedRecovery.recovery().tagGPR()) |
| && !m_lockedRegisters.get(cachedRecovery.recovery().payloadGPR()); |
| } |
| #endif |
| return false; |
| }); |
| } |
| |
| void ensureTempGPR() |
| { |
| if (getFreeTempGPR() != InvalidGPRReg) |
| return; |
| |
| if (verbose) |
| dataLog(" Finding a temp GPR to spill\n"); |
| ensureRegister( |
| [this] (const CachedRecovery& cachedRecovery) { |
| if (cachedRecovery.recovery().isInGPR()) { |
| return !m_lockedRegisters.get(cachedRecovery.recovery().gpr()) |
| && !m_newRegisters[cachedRecovery.recovery().gpr()]; |
| } |
| #if USE(JSVALUE32_64) |
| if (cachedRecovery.recovery().technique() == InPair) { |
| return !m_lockedRegisters.get(cachedRecovery.recovery().tagGPR()) |
| && !m_lockedRegisters.get(cachedRecovery.recovery().payloadGPR()) |
| && !m_newRegisters[cachedRecovery.recovery().tagGPR()] |
| && !m_newRegisters[cachedRecovery.recovery().payloadGPR()]; |
| } |
| #endif |
| return false; |
| }); |
| } |
| |
| void ensureGPR() |
| { |
| if (getFreeGPR() != InvalidGPRReg) |
| return; |
| |
| if (verbose) |
| dataLog(" Finding a GPR to spill\n"); |
| ensureRegister( |
| [this] (const CachedRecovery& cachedRecovery) { |
| if (cachedRecovery.recovery().isInGPR()) |
| return !m_lockedRegisters.get(cachedRecovery.recovery().gpr()); |
| #if USE(JSVALUE32_64) |
| if (cachedRecovery.recovery().technique() == InPair) { |
| return !m_lockedRegisters.get(cachedRecovery.recovery().tagGPR()) |
| && !m_lockedRegisters.get(cachedRecovery.recovery().payloadGPR()); |
| } |
| #endif |
| return false; |
| }); |
| } |
| |
| void ensureFPR() |
| { |
| if (getFreeFPR() != InvalidFPRReg) |
| return; |
| |
| if (verbose) |
| dataLog(" Finding an FPR to spill\n"); |
| ensureRegister( |
| [this] (const CachedRecovery& cachedRecovery) { |
| if (cachedRecovery.recovery().isInFPR()) |
| return !m_lockedRegisters.get(cachedRecovery.recovery().fpr()); |
| return false; |
| }); |
| } |
| |
| CachedRecovery* getNew(JSValueRegs jsValueRegs) const |
| { |
| #if USE(JSVALUE64) |
| return m_newRegisters[jsValueRegs.gpr()]; |
| #else |
| ASSERT( |
| jsValueRegs.tagGPR() == InvalidGPRReg || jsValueRegs.payloadGPR() == InvalidGPRReg |
| || m_newRegisters[jsValueRegs.payloadGPR()] == m_newRegisters[jsValueRegs.tagGPR()]); |
| if (jsValueRegs.payloadGPR() == InvalidGPRReg) |
| return m_newRegisters[jsValueRegs.tagGPR()]; |
| return m_newRegisters[jsValueRegs.payloadGPR()]; |
| #endif |
| } |
| |
| void addNew(JSValueRegs jsValueRegs, ValueRecovery recovery) |
| { |
| ASSERT(jsValueRegs && !getNew(jsValueRegs)); |
| CachedRecovery* cachedRecovery = addCachedRecovery(recovery); |
| #if USE(JSVALUE64) |
| if (cachedRecovery->wantedJSValueRegs()) |
| m_newRegisters[cachedRecovery->wantedJSValueRegs().gpr()] = nullptr; |
| m_newRegisters[jsValueRegs.gpr()] = cachedRecovery; |
| #else |
| if (JSValueRegs oldRegs { cachedRecovery->wantedJSValueRegs() }) { |
| if (oldRegs.payloadGPR()) |
| m_newRegisters[oldRegs.payloadGPR()] = nullptr; |
| if (oldRegs.tagGPR()) |
| m_newRegisters[oldRegs.tagGPR()] = nullptr; |
| } |
| if (jsValueRegs.payloadGPR() != InvalidGPRReg) |
| m_newRegisters[jsValueRegs.payloadGPR()] = cachedRecovery; |
| if (jsValueRegs.tagGPR() != InvalidGPRReg) |
| m_newRegisters[jsValueRegs.tagGPR()] = cachedRecovery; |
| #endif |
| ASSERT(!cachedRecovery->wantedJSValueRegs()); |
| cachedRecovery->setWantedJSValueRegs(jsValueRegs); |
| } |
| |
| void addNew(FPRReg fpr, ValueRecovery recovery) |
| { |
| ASSERT(fpr != InvalidFPRReg && !m_newRegisters[fpr]); |
| CachedRecovery* cachedRecovery = addCachedRecovery(recovery); |
| m_newRegisters[fpr] = cachedRecovery; |
| ASSERT(cachedRecovery->wantedFPR() == InvalidFPRReg); |
| cachedRecovery->setWantedFPR(fpr); |
| } |
| |
| // m_oldFrameBase is the register relative to which we access |
| // slots in the old call frame, with an additional offset of |
| // m_oldFrameOffset. |
| // |
| // - For an actual tail call, m_oldFrameBase is the stack |
| // pointer, and m_oldFrameOffset is the number of locals of the |
| // tail caller's frame. We use such stack pointer-based |
| // addressing because it allows us to load the tail caller's |
| // caller's frame pointer in the frame pointer register |
| // immediately instead of awkwardly keeping it around on the |
| // stack. |
| // |
| // - For a slow path call, m_oldFrameBase is just the frame |
| // pointer, and m_oldFrameOffset is 0. |
| GPRReg m_oldFrameBase { MacroAssembler::framePointerRegister }; |
| int m_oldFrameOffset { 0 }; |
| |
| MacroAssembler::Address addressForOld(VirtualRegister reg) const |
| { |
| return MacroAssembler::Address(m_oldFrameBase, |
| (m_oldFrameOffset + reg.offset()) * sizeof(Register)); |
| } |
| |
| // m_newFrameBase is the register relative to which we access |
| // slots in the new call frame, and we always make it point to |
| // wherever the stack pointer will be right before making the |
| // actual call/jump. The actual base of the new frame is at offset |
| // m_newFrameOffset relative to m_newFrameBase. |
| // |
| // - For an actual tail call, m_newFrameBase is computed |
| // dynamically, and m_newFrameOffset varies between 0 and -2 |
| // depending on the architecture's calling convention (see |
| // prepareForTailCall). |
| // |
| // - For a slow path call, m_newFrameBase is the actual stack |
| // pointer, and m_newFrameOffset is - CallerFrameAndPCSize, |
| // following the convention for a regular call. |
| GPRReg m_newFrameBase { InvalidGPRReg }; |
| int m_newFrameOffset { 0}; |
| |
| bool isUndecided() const |
| { |
| return m_newFrameBase == InvalidGPRReg; |
| } |
| |
| bool isSlowPath() const |
| { |
| return m_newFrameBase == MacroAssembler::stackPointerRegister; |
| } |
| |
| MacroAssembler::Address addressForNew(VirtualRegister reg) const |
| { |
| return MacroAssembler::Address(m_newFrameBase, |
| (m_newFrameOffset + reg.offset()) * sizeof(Register)); |
| } |
| |
| // We use a concept of "danger zone". The danger zone consists of |
| // all the writes in the new frame that could overlap with reads |
| // in the old frame. |
| // |
| // Because we could have a higher actual number of arguments than |
| // parameters, when preparing a tail call, we need to assume that |
| // writing to a slot on the new frame could overlap not only with |
| // the corresponding slot in the old frame, but also with any slot |
| // above it. Thus, the danger zone consists of all writes between |
| // the first write and what I call the "danger frontier": the |
| // highest slot in the old frame we still care about. Thus, the |
| // danger zone contains all the slots between the first slot of |
| // the new frame and the danger frontier. Because the danger |
| // frontier is related to the new frame, it is stored as a virtual |
| // register *in the new frame*. |
| VirtualRegister m_dangerFrontier; |
| |
| VirtualRegister dangerFrontier() const |
| { |
| ASSERT(!isUndecided()); |
| |
| return m_dangerFrontier; |
| } |
| |
| bool isDangerNew(VirtualRegister reg) const |
| { |
| ASSERT(!isUndecided() && isValidNew(reg)); |
| return reg <= dangerFrontier(); |
| } |
| |
| void updateDangerFrontier() |
| { |
| ASSERT(!isUndecided()); |
| |
| m_dangerFrontier = firstNew() - 1; |
| for (VirtualRegister reg = lastNew(); reg >= firstNew(); reg -= 1) { |
| if (!getNew(reg) || !isValidOld(newAsOld(reg)) || !getOld(newAsOld(reg))) |
| continue; |
| |
| m_dangerFrontier = reg; |
| if (verbose) |
| dataLog(" Danger frontier now at NEW ", m_dangerFrontier, "\n"); |
| break; |
| } |
| if (verbose) |
| dataLog(" All clear! Danger zone is empty.\n"); |
| } |
| |
| // A safe write is a write that never writes into the danger zone. |
| bool hasOnlySafeWrites(CachedRecovery& cachedRecovery) const |
| { |
| for (VirtualRegister target : cachedRecovery.targets()) { |
| if (isDangerNew(target)) |
| return false; |
| } |
| return true; |
| } |
| |
| // You must ensure that there is no dangerous writes before |
| // calling this function. |
| bool tryWrites(CachedRecovery&); |
| |
| // This function tries to ensure that there is no longer any |
| // possible safe write, i.e. all remaining writes are either to |
| // the danger zone or callee save restorations. |
| // |
| // It returns false if it was unable to perform some safe writes |
| // due to high register pressure. |
| bool performSafeWrites(); |
| |
| unsigned m_numPassedArgs { UINT_MAX }; |
| }; |
| |
| } // namespace JSC |
| |
| #endif // ENABLE(JIT) |