summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/jit/CallFrameShuffler.cpp
diff options
context:
space:
mode:
authorKonstantin Tokarev <annulen@yandex.ru>2016-08-25 19:20:41 +0300
committerKonstantin Tokarev <annulen@yandex.ru>2017-02-02 12:30:55 +0000
commit6882a04fb36642862b11efe514251d32070c3d65 (patch)
treeb7959826000b061fd5ccc7512035c7478742f7b0 /Source/JavaScriptCore/jit/CallFrameShuffler.cpp
parentab6df191029eeeb0b0f16f127d553265659f739e (diff)
downloadqtwebkit-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.cpp774
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)