diff options
author | Konstantin Tokarev <annulen@yandex.ru> | 2016-08-25 19:20:41 +0300 |
---|---|---|
committer | Konstantin Tokarev <annulen@yandex.ru> | 2017-02-02 12:30:55 +0000 |
commit | 6882a04fb36642862b11efe514251d32070c3d65 (patch) | |
tree | b7959826000b061fd5ccc7512035c7478742f7b0 /Source/JavaScriptCore/jit/CallFrameShuffler.cpp | |
parent | ab6df191029eeeb0b0f16f127d553265659f739e (diff) | |
download | qtwebkit-6882a04fb36642862b11efe514251d32070c3d65.tar.gz |
Imported QtWebKit TP3 (git b57bc6801f1876c3220d5a4bfea33d620d477443)
Change-Id: I3b1d8a2808782c9f34d50240000e20cb38d3680f
Reviewed-by: Konstantin Tokarev <annulen@yandex.ru>
Diffstat (limited to 'Source/JavaScriptCore/jit/CallFrameShuffler.cpp')
-rw-r--r-- | Source/JavaScriptCore/jit/CallFrameShuffler.cpp | 774 |
1 files changed, 774 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/jit/CallFrameShuffler.cpp b/Source/JavaScriptCore/jit/CallFrameShuffler.cpp new file mode 100644 index 000000000..45af55dd6 --- /dev/null +++ b/Source/JavaScriptCore/jit/CallFrameShuffler.cpp @@ -0,0 +1,774 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "config.h" +#include "CallFrameShuffler.h" + +#if ENABLE(JIT) + +#include "CachedRecovery.h" +#include "CCallHelpers.h" +#include "CodeBlock.h" + +namespace JSC { + +CallFrameShuffler::CallFrameShuffler(CCallHelpers& jit, const CallFrameShuffleData& data) + : m_jit(jit) + , m_oldFrame(data.numLocals + JSStack::CallerFrameAndPCSize, nullptr) + , m_newFrame(data.args.size() + JSStack::CallFrameHeaderSize, nullptr) + , m_alignedOldFrameSize(JSStack::CallFrameHeaderSize + + roundArgumentCountToAlignFrame(jit.codeBlock()->numParameters())) + , m_alignedNewFrameSize(JSStack::CallFrameHeaderSize + + roundArgumentCountToAlignFrame(data.args.size())) + , m_frameDelta(m_alignedNewFrameSize - m_alignedOldFrameSize) + , m_lockedRegisters(RegisterSet::allRegisters()) +{ + // We are allowed all the usual registers... + for (unsigned i = GPRInfo::numberOfRegisters; i--; ) + m_lockedRegisters.clear(GPRInfo::toRegister(i)); + for (unsigned i = FPRInfo::numberOfRegisters; i--; ) + m_lockedRegisters.clear(FPRInfo::toRegister(i)); + // ... as well as the runtime registers. + m_lockedRegisters.exclude(RegisterSet::vmCalleeSaveRegisters()); + + ASSERT(!data.callee.isInJSStack() || data.callee.virtualRegister().isLocal()); + addNew(VirtualRegister(JSStack::Callee), data.callee); + + for (size_t i = 0; i < data.args.size(); ++i) { + ASSERT(!data.args[i].isInJSStack() || data.args[i].virtualRegister().isLocal()); + addNew(virtualRegisterForArgument(i), data.args[i]); + } + +#if USE(JSVALUE64) + for (Reg reg = Reg::first(); reg <= Reg::last(); reg = reg.next()) { + if (!data.registers[reg].isSet()) + continue; + + if (reg.isGPR()) + addNew(JSValueRegs(reg.gpr()), data.registers[reg]); + else + addNew(reg.fpr(), data.registers[reg]); + } + + m_tagTypeNumber = data.tagTypeNumber; + if (m_tagTypeNumber != InvalidGPRReg) + lockGPR(m_tagTypeNumber); +#endif +} + +void CallFrameShuffler::dump(PrintStream& out) const +{ + static const char* delimiter = " +-------------------------------+ "; + static const char* dangerDelimiter = " X-------------------------------X "; + static const char* dangerBoundsDelimiter = " XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX "; + static const char* emptySpace = " "; + out.print(" "); + out.print(" Old frame "); + out.print(" New frame "); + out.print("\n"); + int totalSize = m_alignedOldFrameSize + std::max(numLocals(), m_alignedNewFrameSize) + 3; + for (int i = 0; i < totalSize; ++i) { + VirtualRegister old { m_alignedOldFrameSize - i - 1 }; + VirtualRegister newReg { old + m_frameDelta }; + + if (!isValidOld(old) && old != firstOld() - 1 + && !isValidNew(newReg) && newReg != firstNew() - 1) + continue; + + out.print(" "); + if (dangerFrontier() >= firstNew() + && (newReg == dangerFrontier() || newReg == firstNew() - 1)) + out.print(dangerBoundsDelimiter); + else if (isValidOld(old)) + out.print(isValidNew(newReg) && isDangerNew(newReg) ? dangerDelimiter : delimiter); + else if (old == firstOld() - 1) + out.print(delimiter); + else + out.print(emptySpace); + if (dangerFrontier() >= firstNew() + && (newReg == dangerFrontier() || newReg == firstNew() - 1)) + out.print(dangerBoundsDelimiter); + else if (isValidNew(newReg) || newReg == firstNew() - 1) + out.print(isDangerNew(newReg) ? dangerDelimiter : delimiter); + else + out.print(emptySpace); + out.print("\n"); + if (old == firstOld()) + out.print(" sp --> "); + else if (!old.offset()) + out.print(" fp --> "); + else + out.print(" "); + if (isValidOld(old)) { + if (getOld(old)) { + auto str = toCString(old); + if (isValidNew(newReg) && isDangerNew(newReg)) + out.printf(" X %18s X ", str.data()); + else + out.printf(" | %18s | ", str.data()); + } else if (isValidNew(newReg) && isDangerNew(newReg)) + out.printf(" X%30s X ", ""); + else + out.printf(" |%30s | ", ""); + } else + out.print(emptySpace); + if (isValidNew(newReg)) { + const char d = isDangerNew(newReg) ? 'X' : '|'; + auto str = toCString(newReg); + if (getNew(newReg)) { + if (getNew(newReg)->recovery().isConstant()) + out.printf(" %c%8s <- constant %c ", d, str.data(), d); + else { + auto recoveryStr = toCString(getNew(newReg)->recovery()); + out.printf(" %c%8s <- %18s %c ", d, str.data(), + recoveryStr.data(), d); + } + } else if (newReg == VirtualRegister { JSStack::ArgumentCount }) + out.printf(" %c%8s <- %18zu %c ", d, str.data(), argCount(), d); + else + out.printf(" %c%30s %c ", d, "", d); + } else + out.print(emptySpace); + if (newReg == firstNew() - m_newFrameOffset && !isSlowPath()) + out.print(" <-- new sp before jump (current ", m_newFrameBase, ") "); + if (newReg == firstNew()) + out.print(" <-- new fp after prologue"); + out.print("\n"); + } + out.print(" "); + out.print(" Live registers "); + out.print(" Wanted registers "); + out.print("\n"); + for (Reg reg = Reg::first(); reg <= Reg::last(); reg = reg.next()) { + CachedRecovery* oldCachedRecovery { m_registers[reg] }; + CachedRecovery* newCachedRecovery { m_newRegisters[reg] }; + if (!oldCachedRecovery && !newCachedRecovery) + continue; + out.print(" "); + if (oldCachedRecovery) { + auto str = toCString(reg); + out.printf(" %8s ", str.data()); + } else + out.print(emptySpace); +#if USE(JSVALUE32_64) + if (newCachedRecovery) { + JSValueRegs wantedJSValueRegs { newCachedRecovery->wantedJSValueRegs() }; + if (reg.isFPR()) + out.print(reg, " <- ", newCachedRecovery->recovery()); + else { + if (reg.gpr() == wantedJSValueRegs.tagGPR()) + out.print(reg.gpr(), " <- tag(", newCachedRecovery->recovery(), ")"); + else + out.print(reg.gpr(), " <- payload(", newCachedRecovery->recovery(), ")"); + } + } +#else + if (newCachedRecovery) + out.print(" ", reg, " <- ", newCachedRecovery->recovery()); +#endif + out.print("\n"); + } + out.print(" Locked registers: "); + bool firstLocked { true }; + for (Reg reg = Reg::first(); reg <= Reg::last(); reg = reg.next()) { + if (m_lockedRegisters.get(reg)) { + out.print(firstLocked ? "" : ", ", reg); + firstLocked = false; + } + } + out.print("\n"); + + if (isSlowPath()) + out.print(" Using fp-relative addressing for slow path call\n"); + else + out.print(" Using sp-relative addressing for jump (using ", m_newFrameBase, " as new sp)\n"); + if (m_oldFrameOffset) + out.print(" Old frame offset is ", m_oldFrameOffset, "\n"); + if (m_newFrameOffset) + out.print(" New frame offset is ", m_newFrameOffset, "\n"); +#if USE(JSVALUE64) + if (m_tagTypeNumber != InvalidGPRReg) + out.print(" TagTypeNumber is currently in ", m_tagTypeNumber, "\n"); +#endif +} + +CachedRecovery* CallFrameShuffler::getCachedRecovery(ValueRecovery recovery) +{ + ASSERT(!recovery.isConstant()); + if (recovery.isInGPR()) + return m_registers[recovery.gpr()]; + if (recovery.isInFPR()) + return m_registers[recovery.fpr()]; +#if USE(JSVALUE32_64) + if (recovery.technique() == InPair) { + ASSERT(m_registers[recovery.tagGPR()] == m_registers[recovery.payloadGPR()]); + return m_registers[recovery.payloadGPR()]; + } +#endif + ASSERT(recovery.isInJSStack()); + return getOld(recovery.virtualRegister()); +} + +CachedRecovery* CallFrameShuffler::setCachedRecovery(ValueRecovery recovery, CachedRecovery* cachedRecovery) +{ + ASSERT(!recovery.isConstant()); + if (recovery.isInGPR()) + return m_registers[recovery.gpr()] = cachedRecovery; + if (recovery.isInFPR()) + return m_registers[recovery.fpr()] = cachedRecovery; +#if USE(JSVALUE32_64) + if (recovery.technique() == InPair) { + m_registers[recovery.tagGPR()] = cachedRecovery; + return m_registers[recovery.payloadGPR()] = cachedRecovery; + } +#endif + ASSERT(recovery.isInJSStack()); + setOld(recovery.virtualRegister(), cachedRecovery); + return cachedRecovery; +} + +void CallFrameShuffler::spill(CachedRecovery& cachedRecovery) +{ + ASSERT(!isSlowPath()); + ASSERT(cachedRecovery.recovery().isInRegisters()); + + VirtualRegister spillSlot { 0 }; + for (VirtualRegister slot = firstOld(); slot <= lastOld(); slot += 1) { + if (slot >= newAsOld(firstNew())) + break; + + if (getOld(slot)) + continue; + + spillSlot = slot; + break; + } + // We must have enough slots to be able to fit the whole callee's + // frame for the slow path - unless we are in the FTL. In that + // case, we are allowed to extend the frame *once*, since we are + // guaranteed to have enough available space for that. + if (spillSlot >= newAsOld(firstNew()) || !spillSlot.isLocal()) { + RELEASE_ASSERT(!m_didExtendFrame); + extendFrameIfNeeded(); + spill(cachedRecovery); + return; + } + + if (verbose) + dataLog(" * Spilling ", cachedRecovery.recovery(), " into ", spillSlot, "\n"); + auto format = emitStore(cachedRecovery, addressForOld(spillSlot)); + ASSERT(format != DataFormatNone); + updateRecovery(cachedRecovery, ValueRecovery::displacedInJSStack(spillSlot, format)); +} + +void CallFrameShuffler::emitDeltaCheck() +{ + if (ASSERT_DISABLED) + return; + + GPRReg scratchGPR { getFreeGPR() }; + if (scratchGPR != InvalidGPRReg) { + if (verbose) + dataLog(" Using ", scratchGPR, " for the fp-sp delta check\n"); + m_jit.move(MacroAssembler::stackPointerRegister, scratchGPR); + m_jit.subPtr(GPRInfo::callFrameRegister, scratchGPR); + MacroAssembler::Jump ok = m_jit.branch32( + MacroAssembler::Equal, scratchGPR, + MacroAssembler::TrustedImm32(-numLocals() * sizeof(Register))); + m_jit.abortWithReason(JITUnexpectedCallFrameSize); + ok.link(&m_jit); + } else if (verbose) + dataLog(" Skipping the fp-sp delta check since there is too much pressure"); +} + +void CallFrameShuffler::extendFrameIfNeeded() +{ + ASSERT(!m_didExtendFrame); + + VirtualRegister firstRead { firstOld() }; + for (; firstRead <= virtualRegisterForLocal(0); firstRead += 1) { + if (getOld(firstRead)) + break; + } + size_t availableSize = static_cast<size_t>(firstRead.offset() - firstOld().offset()); + size_t wantedSize = m_newFrame.size() + m_newFrameOffset; + + if (availableSize < wantedSize) { + size_t delta = WTF::roundUpToMultipleOf(stackAlignmentRegisters(), wantedSize - availableSize); + m_oldFrame.grow(m_oldFrame.size() + delta); + for (size_t i = 0; i < delta; ++i) + m_oldFrame[m_oldFrame.size() - i - 1] = nullptr; + m_jit.subPtr(MacroAssembler::TrustedImm32(delta * sizeof(Register)), MacroAssembler::stackPointerRegister); + + if (isSlowPath()) + m_frameDelta = numLocals() + JSStack::CallerFrameAndPCSize; + else + m_oldFrameOffset = numLocals(); + + if (verbose) + dataLogF(" Not enough space - extending the old frame %zu slot\n", delta); + } + + m_didExtendFrame = true; +} + +void CallFrameShuffler::prepareForSlowPath() +{ + ASSERT(isUndecided()); + emitDeltaCheck(); + + m_frameDelta = numLocals() + JSStack::CallerFrameAndPCSize; + m_newFrameBase = MacroAssembler::stackPointerRegister; + m_newFrameOffset = -JSStack::CallerFrameAndPCSize; + + if (verbose) + dataLog("\n\nPreparing frame for slow path call:\n"); + + // When coming from the FTL, we need to extend the frame. In other + // cases, we may end up extending the frame if we previously + // spilled things (e.g. in polymorphic cache). + extendFrameIfNeeded(); + + if (verbose) + dataLog(*this); + + prepareAny(); + + if (verbose) + dataLog("Ready for slow path call!\n"); +} + +void CallFrameShuffler::prepareForTailCall() +{ + ASSERT(isUndecided()); + emitDeltaCheck(); + + // We'll use sp-based indexing so that we can load the + // caller's frame pointer into the fpr immediately + m_oldFrameBase = MacroAssembler::stackPointerRegister; + m_oldFrameOffset = numLocals(); + m_newFrameBase = acquireGPR(); +#if CPU(X86) + // We load the frame pointer manually, but we need to ask the + // algorithm to move the return PC for us (it'd probably + // require a write to the danger zone). Since it'd be awkward + // to ask for half a value move, we ask that the whole thing + // be moved for us. + addNew(VirtualRegister { 0 }, + ValueRecovery::displacedInJSStack(VirtualRegister(0), DataFormatJS)); + + // sp will point to head0 and we will move it up half a slot + // manually + m_newFrameOffset = 0; +#elif CPU(ARM) || CPU(SH4) || CPU(MIPS) + // We load the the frame pointer and link register + // manually. We could ask the algorithm to load them for us, + // and it would allow us to use the link register as an extra + // temporary - but it'd mean that the frame pointer can also + // be used as an extra temporary, so we keep the link register + // locked instead. + + // sp will point to head1 since the callee's prologue pushes + // the call frame and link register. + m_newFrameOffset = -1; +#elif CPU(ARM64) + // We load the frame pointer and link register manually. We + // could ask the algorithm to load the link register for us + // (which would allow for its use as an extra temporary), but + // since its not in GPRInfo, we can't do it. + + // sp will point to head2 since the callee's prologue pushes the + // call frame and link register + m_newFrameOffset = -2; +#elif CPU(X86_64) + // We load the frame pointer manually, but we ask the + // algorithm to move the return PC for us (it'd probably + // require a write in the danger zone) + addNew(VirtualRegister { 1 }, + ValueRecovery::displacedInJSStack(VirtualRegister(1), DataFormatJS)); + + // sp will point to head1 since the callee's prologue pushes + // the call frame register + m_newFrameOffset = -1; +#else + UNREACHABLE_FOR_PLATFORM(); +#endif + + if (verbose) + dataLog(" Emitting code for computing the new frame base\n"); + + // We compute the new frame base by first computing the top of the + // old frame (taking into account an argument count higher than + // the number of parameters), then substracting to it the aligned + // new frame size (adjusted). + m_jit.load32(MacroAssembler::Address(GPRInfo::callFrameRegister, JSStack::ArgumentCount * static_cast<int>(sizeof(Register)) + PayloadOffset), m_newFrameBase); + MacroAssembler::Jump argumentCountOK = + m_jit.branch32(MacroAssembler::BelowOrEqual, m_newFrameBase, + MacroAssembler::TrustedImm32(m_jit.codeBlock()->numParameters())); + m_jit.add32(MacroAssembler::TrustedImm32(stackAlignmentRegisters() - 1 + JSStack::CallFrameHeaderSize), m_newFrameBase); + m_jit.and32(MacroAssembler::TrustedImm32(-stackAlignmentRegisters()), m_newFrameBase); + m_jit.mul32(MacroAssembler::TrustedImm32(sizeof(Register)), m_newFrameBase, m_newFrameBase); + MacroAssembler::Jump done = m_jit.jump(); + argumentCountOK.link(&m_jit); + m_jit.move( + MacroAssembler::TrustedImm32(m_alignedOldFrameSize * sizeof(Register)), + m_newFrameBase); + done.link(&m_jit); + + m_jit.addPtr(GPRInfo::callFrameRegister, m_newFrameBase); + m_jit.subPtr( + MacroAssembler::TrustedImm32( + (m_alignedNewFrameSize + m_newFrameOffset) * sizeof(Register)), + m_newFrameBase); + + // We load the link register manually for architectures that have one +#if CPU(ARM) || CPU(SH4) || CPU(ARM64) + m_jit.loadPtr(MacroAssembler::Address(MacroAssembler::framePointerRegister, sizeof(void*)), + MacroAssembler::linkRegister); +#elif CPU(MIPS) + m_jit.loadPtr(MacroAssembler::Address(MacroAssembler::framePointerRegister, sizeof(void*)), + MacroAssembler::returnAddressRegister); +#endif + + // We want the frame pointer to always point to a valid frame, and + // we are going to trash the current one. Let's make it point to + // our caller's frame, since that's what we want to end up with. + m_jit.loadPtr(MacroAssembler::Address(MacroAssembler::framePointerRegister), + MacroAssembler::framePointerRegister); + + if (verbose) + dataLog("Preparing frame for tail call:\n", *this); + + prepareAny(); + +#if CPU(X86) + if (verbose) + dataLog(" Simulating pop of the call frame register\n"); + m_jit.addPtr(MacroAssembler::TrustedImm32(sizeof(void*)), MacroAssembler::stackPointerRegister); +#endif + + if (verbose) + dataLog("Ready for tail call!\n"); +} + +bool CallFrameShuffler::tryWrites(CachedRecovery& cachedRecovery) +{ + ASSERT(m_newFrameBase != InvalidGPRReg); + + // If the value is already set up correctly, we don't have + // anything to do. + if (isSlowPath() && cachedRecovery.recovery().isInJSStack() + && cachedRecovery.targets().size() == 1 + && newAsOld(cachedRecovery.targets()[0]) == cachedRecovery.recovery().virtualRegister()) { + cachedRecovery.clearTargets(); + if (!cachedRecovery.wantedJSValueRegs() && cachedRecovery.wantedFPR() == InvalidFPRReg) + clearCachedRecovery(cachedRecovery.recovery()); + return true; + } + + if (!canLoadAndBox(cachedRecovery)) + return false; + + emitLoad(cachedRecovery); + emitBox(cachedRecovery); + ASSERT(cachedRecovery.recovery().isInRegisters() + || cachedRecovery.recovery().isConstant()); + + if (verbose) + dataLog(" * Storing ", cachedRecovery.recovery()); + for (size_t i = 0; i < cachedRecovery.targets().size(); ++i) { + VirtualRegister target { cachedRecovery.targets()[i] }; + ASSERT(!isDangerNew(target)); + if (verbose) + dataLog(!i ? " into " : ", and ", "NEW ", target); + emitStore(cachedRecovery, addressForNew(target)); + setNew(target, nullptr); + } + if (verbose) + dataLog("\n"); + cachedRecovery.clearTargets(); + if (!cachedRecovery.wantedJSValueRegs() && cachedRecovery.wantedFPR() == InvalidFPRReg) + clearCachedRecovery(cachedRecovery.recovery()); + + return true; +} + +bool CallFrameShuffler::performSafeWrites() +{ + VirtualRegister firstSafe; + VirtualRegister end { lastNew() + 1 }; + Vector<VirtualRegister> failures; + + // For all cachedRecoveries that writes to the safe zone, if it + // doesn't also write to the danger zone, we try to perform + // the writes. This may free up danger slots, so we iterate + // again until it doesn't happen anymore. + // + // Note that even though we have a while block, we look at + // each slot of the new call frame at most once since in each + // iteration beyond the first, we only load up the portion of + // the new call frame that was dangerous and became safe due + // to the previous iteration. + do { + firstSafe = dangerFrontier() + 1; + if (verbose) + dataLog(" Trying safe writes (between NEW ", firstSafe, " and NEW ", end - 1, ")\n"); + bool didProgress = false; + for (VirtualRegister reg = firstSafe; reg < end; reg += 1) { + CachedRecovery* cachedRecovery = getNew(reg); + if (!cachedRecovery) { + if (verbose) + dataLog(" + ", reg, " is OK.\n"); + continue; + } + if (!hasOnlySafeWrites(*cachedRecovery)) { + if (verbose) { + dataLog(" - ", cachedRecovery->recovery(), " writes to NEW ", reg, + " but also has dangerous writes.\n"); + } + continue; + } + if (cachedRecovery->wantedJSValueRegs()) { + if (verbose) { + dataLog(" - ", cachedRecovery->recovery(), " writes to NEW ", reg, + " but is also needed in registers.\n"); + } + continue; + } + if (cachedRecovery->wantedFPR() != InvalidFPRReg) { + if (verbose) { + dataLog(" - ", cachedRecovery->recovery(), " writes to NEW ", reg, + " but is also needed in an FPR.\n"); + } + continue; + } + if (!tryWrites(*cachedRecovery)) { + if (verbose) + dataLog(" - Unable to write to NEW ", reg, " from ", cachedRecovery->recovery(), "\n"); + failures.append(reg); + } + didProgress = true; + } + end = firstSafe; + + // If we have cachedRecoveries that failed to write, it is + // because they are on the stack and we didn't have enough + // registers available at the time to load them into. If + // we have a free register, we should try again because it + // could free up some danger slots. + if (didProgress && hasFreeRegister()) { + Vector<VirtualRegister> stillFailing; + for (VirtualRegister failed : failures) { + CachedRecovery* cachedRecovery = getNew(failed); + // It could have been handled later if it had + // several targets + if (!cachedRecovery) + continue; + + ASSERT(hasOnlySafeWrites(*cachedRecovery) + && !cachedRecovery->wantedJSValueRegs() + && cachedRecovery->wantedFPR() == InvalidFPRReg); + if (!tryWrites(*cachedRecovery)) + stillFailing.append(failed); + } + failures = WTFMove(stillFailing); + } + if (verbose && firstSafe != dangerFrontier() + 1) + dataLog(" We freed up danger slots!\n"); + } while (firstSafe != dangerFrontier() + 1); + + return failures.isEmpty(); +} + +void CallFrameShuffler::prepareAny() +{ + ASSERT(!isUndecided()); + + updateDangerFrontier(); + + // First, we try to store any value that goes above the danger + // frontier. This will never use more registers since we are only + // loading+storing if we ensure that any register used for the load + // will be freed up after the stores (i.e., all stores are above + // the danger frontier, and there is no wanted register). + performSafeWrites(); + + // At this point, we couldn't have more available registers than + // we have withouth spilling: all values currently in registers + // either require a write to the danger zone, or have a wanted + // register, which means that in any case they will have to go + // through registers again. + + // We now slowly free up the danger zone by first loading the old + // value on the danger frontier, spilling as many registers as + // needed to do so and ensuring that the corresponding slot in the + // new frame is now ready to be written. Then, we store the old + // value to its target location if possible (we could have failed + // to load it previously due to high pressure). Finally, we write + // to any of the newly safe slots that we can, which could free up + // registers (hence why we do it eagerly). + for (VirtualRegister reg = dangerFrontier(); reg >= firstNew(); reg -= 1) { + if (reg == dangerFrontier()) { + if (verbose) + dataLog(" Next slot (NEW ", reg, ") is the danger frontier\n"); + CachedRecovery* cachedRecovery { getOld(newAsOld(dangerFrontier())) }; + ASSERT(cachedRecovery); + ensureLoad(*cachedRecovery); + emitLoad(*cachedRecovery); + ensureBox(*cachedRecovery); + emitBox(*cachedRecovery); + if (hasOnlySafeWrites(*cachedRecovery)) + tryWrites(*cachedRecovery); + } else if (verbose) + dataLog(" Next slot is NEW ", reg, "\n"); + + ASSERT(!isDangerNew(reg)); + CachedRecovery* cachedRecovery = getNew(reg); + // This could be one of the header slots we don't care about. + if (!cachedRecovery) { + if (verbose) + dataLog(" + ", reg, " is OK\n"); + continue; + } + + if (canLoadAndBox(*cachedRecovery) && hasOnlySafeWrites(*cachedRecovery) + && !cachedRecovery->wantedJSValueRegs() + && cachedRecovery->wantedFPR() == InvalidFPRReg) { + emitLoad(*cachedRecovery); + emitBox(*cachedRecovery); + bool writesOK = tryWrites(*cachedRecovery); + ASSERT_UNUSED(writesOK, writesOK); + } else if (verbose) + dataLog(" - ", cachedRecovery->recovery(), " can't be handled just yet.\n"); + } + ASSERT(dangerFrontier() < firstNew()); + + // Now, the danger zone is empty, but we still have a couple of + // things to do: + // + // 1) There could be remaining safe writes that failed earlier due + // to high register pressure and had nothing to do with the + // danger zone whatsoever. + // + // 2) Some wanted registers could have to be loaded (this could + // happen either when making a call to a new function with a + // lower number of arguments - since above here, we only load + // wanted registers when they are at the danger frontier -, or + // if a wanted register got spilled). + // + // 3) Some wanted registers could have been loaded in the wrong + // registers + // + // 4) We have to take care of some bookkeeping - namely, storing + // the argument count and updating the stack pointer. + + // At this point, we must have enough registers available for + // handling 1). None of the loads can fail because we have been + // eagerly freeing up registers in all the previous phases - so + // the only values that are in registers at this point must have + // wanted registers. + if (verbose) + dataLog(" Danger zone is clear, performing remaining writes.\n"); + for (VirtualRegister reg = firstNew(); reg <= lastNew(); reg += 1) { + CachedRecovery* cachedRecovery { getNew(reg) }; + if (!cachedRecovery) + continue; + + emitLoad(*cachedRecovery); + emitBox(*cachedRecovery); + bool writesOK = tryWrites(*cachedRecovery); + ASSERT_UNUSED(writesOK, writesOK); + } + +#if USE(JSVALUE64) + if (m_tagTypeNumber != InvalidGPRReg && m_newRegisters[m_tagTypeNumber]) + releaseGPR(m_tagTypeNumber); +#endif + + // Handle 2) by loading all registers. We don't have to do any + // writes, since they have been taken care of above. + if (verbose) + dataLog(" Loading wanted registers into registers\n"); + for (Reg reg = Reg::first(); reg <= Reg::last(); reg = reg.next()) { + CachedRecovery* cachedRecovery { m_newRegisters[reg] }; + if (!cachedRecovery) + continue; + + emitLoad(*cachedRecovery); + emitBox(*cachedRecovery); + ASSERT(cachedRecovery->targets().isEmpty()); + } + +#if USE(JSVALUE64) + if (m_tagTypeNumber != InvalidGPRReg) + releaseGPR(m_tagTypeNumber); +#endif + + // At this point, we have read everything we cared about from the + // stack, and written everything we had to to the stack. + if (verbose) + dataLog(" Callee frame is fully set up\n"); + if (!ASSERT_DISABLED) { + for (VirtualRegister reg = firstNew(); reg <= lastNew(); reg += 1) + ASSERT_UNUSED(reg, !getNew(reg)); + + for (CachedRecovery* cachedRecovery : m_cachedRecoveries) { + ASSERT_UNUSED(cachedRecovery, cachedRecovery->targets().isEmpty()); + ASSERT(!cachedRecovery->recovery().isInJSStack()); + } + } + + // We need to handle 4) first because it implies releasing + // m_newFrameBase, which could be a wanted register. + if (verbose) + dataLog(" * Storing the argument count into ", VirtualRegister { JSStack::ArgumentCount }, "\n"); + m_jit.store32(MacroAssembler::TrustedImm32(0), + addressForNew(VirtualRegister { JSStack::ArgumentCount }).withOffset(TagOffset)); + m_jit.store32(MacroAssembler::TrustedImm32(argCount()), + addressForNew(VirtualRegister { JSStack::ArgumentCount }).withOffset(PayloadOffset)); + + if (!isSlowPath()) { + ASSERT(m_newFrameBase != MacroAssembler::stackPointerRegister); + if (verbose) + dataLog(" Releasing the new frame base pointer\n"); + m_jit.move(m_newFrameBase, MacroAssembler::stackPointerRegister); + releaseGPR(m_newFrameBase); + } + + // Finally we handle 3) + if (verbose) + dataLog(" Ensuring wanted registers are in the right register\n"); + for (Reg reg = Reg::first(); reg <= Reg::last(); reg = reg.next()) { + CachedRecovery* cachedRecovery { m_newRegisters[reg] }; + if (!cachedRecovery) + continue; + + emitDisplace(*cachedRecovery); + } +} + +} // namespace JSC + +#endif // ENABLE(JIT) |