aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/jit/qv4schedulers.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/jit/qv4schedulers.cpp')
-rw-r--r--src/qml/jit/qv4schedulers.cpp912
1 files changed, 912 insertions, 0 deletions
diff --git a/src/qml/jit/qv4schedulers.cpp b/src/qml/jit/qv4schedulers.cpp
new file mode 100644
index 0000000000..0dffefa951
--- /dev/null
+++ b/src/qml/jit/qv4schedulers.cpp
@@ -0,0 +1,912 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qloggingcategory.h>
+
+#include "qv4schedulers_p.h"
+#include "qv4util_p.h"
+#include "qv4graph_p.h"
+#include "qv4blockscheduler_p.h"
+#include "qv4stackframe_p.h"
+
+QT_BEGIN_NAMESPACE
+namespace QV4 {
+namespace IR {
+
+Q_LOGGING_CATEGORY(lcSched, "qt.v4.ir.scheduling")
+Q_LOGGING_CATEGORY(lcDotCFG, "qt.v4.ir.scheduling.cfg")
+
+static bool needsScheduling(Node *n)
+{
+ if (n->operation()->isConstant())
+ return false;
+ switch (n->opcode()) {
+ case Meta::Function: Q_FALLTHROUGH();
+ case Meta::CppFrame:
+ case Meta::Phi:
+ case Meta::EffectPhi:
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool NodeScheduler::canStartBlock(Node *node) const
+{
+ switch (node->operation()->kind()) {
+ case Meta::Start: Q_FALLTHROUGH();
+ case Meta::IfTrue:
+ case Meta::IfFalse:
+ case Meta::Region:
+ case Meta::HandleUnwind:
+ case Meta::OnException:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+bool NodeScheduler::isControlFlowSplit(Node *node) const
+{
+ int nOutputs = node->operation()->controlOutputCount();
+ if (nOutputs == 2) {
+ // if there is a "missing" control output, it's for exception flow without unwinder
+ int controlUses = 0;
+ auto uses = node->uses();
+ for (auto it = uses.begin(), eit = uses.end(); it != eit; ++it) {
+ if (isLive(*it) && it.isUsedAsControl())
+ ++controlUses;
+ }
+ return controlUses == 2;
+ }
+ return nOutputs > 2;
+}
+
+bool NodeScheduler::isBlockTerminator(Node *node) const
+{
+ switch (node->operation()->kind()) {
+ case Meta::Branch: Q_FALLTHROUGH();
+ case Meta::Jump:
+ case Meta::Return:
+ case Meta::TailCall:
+ case Meta::UnwindDispatch:
+ case Meta::End:
+ return true;
+ case Meta::Call:
+ return isControlFlowSplit(node);
+ default:
+ return false;
+ }
+}
+
+MIBlock *NodeScheduler::getCommonDominator(MIBlock *one, MIBlock *other) const
+{
+ MIBlock::Index a = one->index();
+ MIBlock::Index b = other->index();
+
+ while (a != b) {
+ if (m_dominatorDepthForBlock[a] < m_dominatorDepthForBlock[b])
+ b = m_domTree->immediateDominator(b);
+ else
+ a = m_domTree->immediateDominator(a);
+ }
+
+ return m_miFunction->block(a);
+}
+
+// For Nodes that end up inside loops, it'd be great if we can move (hoist) them out of the loop.
+// To do that, we need a block that preceeds the loop. (So the block before the loop header.)
+// This function calculates that hoist block if the original block is in a loop.
+MIBlock *NodeScheduler::getHoistBlock(MIBlock *block) const
+{
+ if (m_loopInfo->isLoopHeader(block))
+ return m_miFunction->block(m_domTree->immediateDominator(block->index()));
+
+ // make the loop header a candidate:
+ MIBlock *loopHeader = m_loopInfo->loopHeaderFor(block);
+ if (loopHeader == nullptr)
+ return nullptr; // block is not in a loop
+
+ // And now the tricky part: block has to dominate all exits from the loop. If it does not do
+ // that, it meanse that there is an exit from the loop that can be reached before block. In
+ // that case, hoisting from "block" to "loopHeader" would mean there now is an extra calculation
+ // that is not needed for a certain loop exit.
+ for (MIBlock *outEdge : m_loopInfo->loopExitsForLoop(loopHeader)) {
+ if (getCommonDominator(block, outEdge) != block)
+ return nullptr;
+ }
+
+ return m_miFunction->block(m_domTree->immediateDominator(loopHeader->index()));
+}
+
+NodeScheduler::NodeScheduler(Function *irFunction)
+ : m_irFunction(irFunction)
+ , m_vregs(irFunction->graph()->nodeCount(), std::numeric_limits<unsigned>::max())
+ , m_live(irFunction->graph(), /*collectUses =*/ false /* do explicitly NOT collect uses! */)
+{
+}
+
+MIFunction *NodeScheduler::buildMIFunction()
+{
+ m_miFunction = new MIFunction(m_irFunction);
+
+ // step 1: build the CFG
+ auto roots = buildCFG();
+ m_miFunction->renumberBlocks();
+ m_miFunction->dump(QStringLiteral("CFG after renumbering"));
+
+ Q_ASSERT(m_miFunction->block(MIFunction::StartBlockIndex)->index()
+ == MIFunction::StartBlockIndex);
+ Q_ASSERT(m_miFunction->block(MIFunction::StartBlockIndex)->instructions().front().opcode()
+ == Meta::Start);
+
+ // step 2: build the dominator tree
+ if (lcDotCFG().isDebugEnabled())
+ dumpDotCFG();
+ m_domTree.reset(new DominatorTree(m_miFunction));
+ m_dominatorDepthForBlock = m_domTree->calculateNodeDepths();
+
+ // step 3: find loops
+ m_loopInfo.reset(new LoopInfo(*m_domTree.data()));
+ m_loopInfo->detectLoops();
+
+ // step 4: schedule early
+ scheduleEarly(roots);
+ showNodesByBlock(QStringLiteral("nodes per block after early scheduling"));
+
+ // step 5: schedule late
+ scheduleLate(roots);
+ showNodesByBlock(QStringLiteral("nodes per block after late scheduling"));
+
+ // step 6: schedule instructions in each block
+ scheduleNodesInBlock();
+
+ m_miFunction->dump(QStringLiteral("MI before block scheduling"));
+
+ // step 7: order the basic blocks in the CFG
+ BlockScheduler blockScheduler(*m_domTree.data(), *m_loopInfo.data());
+ m_miFunction->setBlockOrder(blockScheduler.scheduledBlockSequence());
+
+ // we're done
+ m_miFunction->renumberInstructions();
+ m_miFunction->setVregCount(m_nextVReg);
+ m_miFunction->dump(QStringLiteral("MI after scheduling"));
+ return m_miFunction;
+}
+
+static Node *splitEdge(Function *irFunction, Node *node, unsigned inputIndex)
+{
+ Graph *g = irFunction->graph();
+ Node *in = node->input(inputIndex);
+ Node *region = g->createNode(g->opBuilder()->getRegion(1), &in, 1);
+ Node *jump = g->createNode(g->opBuilder()->get<Meta::Jump>(), &region, 1);
+
+ qCDebug(lcSched) << "splitting critical edge from node" << node->id()
+ << "to node" << node->input(inputIndex)->id()
+ << "by inserting jump node" << jump->id()
+ << "and region node" << region->id();
+
+ node->replaceInput(inputIndex, jump);
+ return jump;
+}
+
+// See Chapter 6.3.1 of https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf for
+// a description of the algorithm.
+std::vector<Node *> NodeScheduler::buildCFG()
+{
+ std::vector<Node *> roots;
+ roots.reserve(32);
+ NodeWorkList todo(m_irFunction->graph());
+
+ auto enqueueControlInputs = [this, &todo](Node *node) {
+ for (unsigned i = 0, ei = node->operation()->controlInputCount(); i != ei; ++i) {
+ const auto inputIndex = node->operation()->indexOfFirstControl() + i;
+ Node *input = node->input(inputIndex);
+ Q_ASSERT(input);
+ if (node->operation()->kind() == Meta::Region
+ && node->operation()->controlInputCount() > 1
+ && isControlFlowSplit(input)) {
+ // critical edge!
+ input = splitEdge(m_irFunction, node, inputIndex);
+ m_live.markReachable(input);
+ m_live.markReachable(input->controlInput(0));
+ }
+ if (!isBlockTerminator(input)) {
+ auto g = m_irFunction->graph();
+ Node *jump = g->createNode(g->opBuilder()->get<Meta::Jump>(), &input, 1);
+ node->replaceInput(inputIndex, jump);
+ m_live.markReachable(jump);
+ qCDebug(lcSched) << "inserting jump node" << jump->id()
+ << "between node" << node->id()
+ << "and node" << input->id();
+ input = jump;
+ }
+ todo.enqueue(input);
+ }
+ };
+
+ // create the CFG by scheduling control dependencies that start/end blocks:
+ todo.enqueue(m_irFunction->graph()->endNode());
+ while (Node *node = todo.dequeueNextNodeForVisiting()) {
+ Q_ASSERT(isBlockTerminator(node));
+
+ if (schedulerData(node)->minimumBlock)
+ continue;
+
+ MIBlock *b = m_miFunction->addBlock();
+
+ qCDebug(lcSched) << "scheduling node" << node->id() << "as terminator for new block"
+ << b->index();
+ b->instructions().push_front(createMIInstruction(node));
+ placeFixed(node, b, Schedule);
+ roots.push_back(node);
+
+ if (Node *framestate = node->frameStateInput()) {
+ placeFixed(framestate, b, DontSchedule);
+ qCDebug(lcSched) << ".. also scheduling framestate dependency node" << node->id()
+ << "in block" << b->index();
+ }
+
+ if (node->opcode() == Meta::End) {
+ enqueueControlInputs(node);
+ continue;
+ }
+
+ while (true) {
+ Node *controlDependency = node->controlInput(0);
+ if (!controlDependency)
+ break;
+ if (todo.isVisited(controlDependency))
+ break;
+ if (schedulerData(controlDependency)->isFixed)
+ break;
+
+ if (controlDependency->opcode() == Meta::Start) {
+ qCDebug(lcSched) << "placing start node" << controlDependency->id()
+ << "in block" << b->index();
+ handleStartNode(controlDependency, b);
+ placeFixed(controlDependency, b, Schedule);
+ roots.push_back(controlDependency);
+ break; // we're done with this block
+ }
+ if (isBlockTerminator(controlDependency)) {
+ qCDebug(lcSched) << "found terminator node" << controlDependency->id()
+ << "for another block, so finish block" << b->index();
+ Node *merge = m_irFunction->graph()->createNode(
+ m_irFunction->graph()->opBuilder()->getRegion(1), &controlDependency, 1);
+ node->replaceInput(node->operation()->indexOfFirstControl(), merge);
+ addBlockStart(roots, merge, b);
+ placeFixed(merge, b, Schedule);
+ m_live.markReachable(merge);
+ todo.enqueue(controlDependency);
+ break; // we're done with this block
+ }
+ if (canStartBlock(controlDependency)
+ || schedulerData(controlDependency->controlInput())->isFixed) {
+ qCDebug(lcSched) << "found block start node" << controlDependency->id()
+ << "for this block, so finish block" << b->index();
+ addBlockStart(roots, controlDependency, b);
+ placeFixed(controlDependency, b, Schedule);
+ roots.push_back(controlDependency);
+ enqueueControlInputs(controlDependency);
+ break; // we're done with this block
+ }
+ qCDebug(lcSched) << "skipping node" << controlDependency->id();
+ node = controlDependency;
+ }
+ }
+
+ // link the edges of the MIBlocks, and add basic-block arguments:
+ for (MIBlock *toBlock : m_miFunction->blocks()) {
+ Q_ASSERT(!toBlock->instructions().empty());
+ MIInstr &instr = toBlock->instructions().front();
+ Node *toNode = instr.irNode();
+ const auto opcode = toNode->operation()->kind();
+ if (opcode == Meta::Region) {
+ unsigned inputNr = 0;
+ for (Node *input : toNode->inputs()) {
+ MIBlock *fromBlock = schedulerData(input)->minimumBlock;
+ fromBlock->addOutEdge(toBlock);
+ toBlock->addInEdge(fromBlock);
+ MIInstr &fromTerminator = fromBlock->instructions().back();
+ if (fromTerminator.irNode()->opcode() == Meta::Jump ||
+ fromTerminator.irNode()->opcode() == Meta::UnwindDispatch) {
+ unsigned arg = 0;
+ for (const MIOperand &bbArg : toBlock->arguments()) {
+ fromTerminator.setOperand(arg++,
+ createMIOperand(bbArg.irNode()->input(inputNr)));
+ }
+ }
+ ++inputNr;
+ }
+ } else if (opcode == Meta::End) {
+ for (Node *input : toNode->inputs()) {
+ MIBlock *fromBlock = schedulerData(input)->minimumBlock;
+ fromBlock->addOutEdge(toBlock);
+ toBlock->addInEdge(fromBlock);
+ }
+ } else if (Node *fromNode = toNode->controlInput()) {
+ MIBlock *fromBlock = schedulerData(fromNode)->minimumBlock;
+ fromBlock->addOutEdge(toBlock);
+ toBlock->addInEdge(fromBlock);
+ }
+ }
+
+ m_irFunction->dump(QStringLiteral("graph after building CFG"));
+
+ auto startBlock = schedulerData(m_irFunction->graph()->startNode())->minimumBlock;
+ m_miFunction->setStartBlock(startBlock);
+
+ if (lcSched().isDebugEnabled())
+ m_miFunction->dump(QStringLiteral("control flow graph before renumbering"));
+ m_miFunction->verifyCFG();
+
+ return roots;
+}
+
+// See Chapter 6.3.3 of https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf for
+// a description of the algorithm.
+void NodeScheduler::scheduleEarly(const std::vector<Node *> &roots)
+{
+ // scheduling one node might have the effect of queueing its dependencies
+ NodeWorkList todo(m_irFunction->graph());
+ for (Node *root : roots) {
+ todo.enqueue(root);
+ while (Node *node = todo.dequeueNextNodeForVisiting())
+ scheduleEarly(node, todo);
+ }
+}
+
+void NodeScheduler::scheduleEarly(Node *node, NodeWorkList &todo)
+{
+ qCDebug(lcSched) << "Scheduling node" << node->id() << "early...";
+
+ SchedulerData *sd = schedulerData(node);
+
+ if (sd->isFixed) {
+ // Fixed nodes already know their schedule early position.
+ qCDebug(lcSched) << ".. Fixed node" << node->id() << "is on minimum block"
+ << sd->minimumBlock->index()
+ << "which has dominator depth"
+ << m_dominatorDepthForBlock[sd->minimumBlock->index()];
+ }
+
+ for (Node *use : node->uses()) {
+ if (isLive(use))
+ propagateMinimumPosition(sd->minimumBlock, use, todo);
+ else
+ qCDebug(lcSched) << ".. Skipping node" << use->id() << "as it's not live";
+ }
+}
+
+void NodeScheduler::propagateMinimumPosition(MIBlock *newMinimumPosition, Node *toNode,
+ NodeWorkList &todo)
+{
+ Q_ASSERT(newMinimumPosition);
+
+ SchedulerData *sd = schedulerData(toNode);
+ if (sd->isFixed) // nothing to do
+ return;
+
+ MIBlock::Index minimumBlockIndex = sd->minimumBlock
+ ? sd->minimumBlock->index()
+ : MIFunction::StartBlockIndex;
+ Q_ASSERT(m_domTree->insideSameDominatorChain(newMinimumPosition->index(), minimumBlockIndex));
+ if (sd->minimumBlock == nullptr
+ || m_dominatorDepthForBlock[newMinimumPosition->index()]
+ > m_dominatorDepthForBlock[minimumBlockIndex]) {
+ // ok, some input for toNode is scheduled *after* our current minimum depth, so we need
+ // to adjust out minimal position. (This might involve rescheduling toNode's uses.)
+ place(toNode, newMinimumPosition);
+ todo.reEnqueue(toNode);
+ qCDebug(lcSched) << ".. Propagating minimum block" << sd->minimumBlock->index()
+ << "which has dominator depth"
+ << m_dominatorDepthForBlock[newMinimumPosition->index()]
+ << "to use node" << toNode->id();
+ } else {
+ qCDebug(lcSched) << ".. Minimum position" << newMinimumPosition->index()
+ << "is not better than" << minimumBlockIndex
+ << "for node" << toNode->id();
+ }
+}
+
+// See Chapter 6.3.4 of https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf for
+// a description of the algorithm.
+//
+// There is one extra detail not described in the thesis mentioned above: loop hoisting. Before we
+// place a node in the latest block that dominates all uses, we check if we accidentally sink it
+// *into* a loop (meaning the latest block is inside a loop, where it is not if the earliest
+// possible block would be chosen). If we detect that a nodes is going to sink into a loop, we walk
+// the dominator path from the latest block up to the earliest block, and pick the first block that
+// is in the same loop (if any) as the earlieast block.
+//
+// As noted in the thesis, this strategy might enlongen life times, which could be harmful for
+// values that are simple to re-materialized or re-calculate.
+void NodeScheduler::scheduleLate(const std::vector<Node *> &roots)
+{
+ NodeWorkList todo(m_irFunction->graph());
+ for (Node *root : roots)
+ todo.enqueue(root);
+
+ while (Node *node = todo.dequeueNextNodeForVisiting())
+ scheduleNodeLate(node, todo);
+}
+
+void NodeScheduler::scheduleNodeLate(Node *node, NodeWorkList &todo)
+{
+ if (!needsScheduling(node))
+ return;
+ qCDebug(lcSched) << "Scheduling node" << node->id() << "late...";
+
+ auto sd = schedulerData(node);
+ if (sd->unscheduledUses == SchedulerData::NotYetCalculated) {
+ sd->unscheduledUses = 0;
+ for (Node *use : node->uses()) {
+ if (!isLive(use))
+ continue;
+ if (!needsScheduling(use))
+ continue;
+ if (schedulerData(use)->isFixed)
+ continue;
+ todo.enqueue(use);
+ ++sd->unscheduledUses;
+ }
+ }
+
+ if (sd->isFixed) {
+ qCDebug(lcSched) << ".. it's fixed";
+ enqueueInputs(node, todo);
+ return;
+ }
+
+ if (sd->unscheduledUses) {
+ qCDebug(lcSched).noquote() << ".. not all uses are fixed, postpone it."<< todo.status(node);
+ return;
+ }
+
+ MIBlock *&minBlock = sd->minimumBlock;
+ if (minBlock == nullptr)
+ minBlock = m_miFunction->block(MIFunction::StartBlockIndex);
+ MIBlock *commonUseDominator = commonDominatorOfUses(node);
+ qCDebug(lcSched) << ".. common use dominator: block" << commonUseDominator->index();
+
+ // the minBlock has to dominate the uses, *and* the common dominator of the uses.
+ Q_ASSERT(minBlock->index() == commonUseDominator->index() ||
+ m_domTree->dominates(minBlock->index(), commonUseDominator->index()));
+
+ // we now found the deepest block, so use it as the target block:
+ MIBlock *targetBlock = commonUseDominator;
+
+ if (node->opcode() == Meta::FrameState) {
+ // never hoist framestates: they're used (among other things) to keep their inputs alive, so
+ // hoisting them out would end the life-time of those inputs prematurely
+ } else {
+ // but we want to prevent blocks sinking into loops unnecessary
+ MIBlock *hoistBlock = getHoistBlock(targetBlock);
+ while (hoistBlock
+ && m_dominatorDepthForBlock[hoistBlock->index()]
+ >= m_dominatorDepthForBlock[minBlock->index()]) {
+ qCDebug(lcSched) << ".. hoisting node" << node->id() << "from block"
+ << targetBlock->index() << "to block" << hoistBlock->index();
+ // ok, so there a) is a hoist block and b) it's deeper than the minimum block,
+ // so lift it up one level ...
+ targetBlock = hoistBlock;
+ // ... and see if we can lift it one more level
+ hoistBlock = getHoistBlock(targetBlock);
+ }
+ }
+
+ qCDebug(lcSched) << ".. fixating it in block" << targetBlock->index()
+ << "where the minimum block was" << minBlock->index();
+
+ placeFixed(node, targetBlock, DontSchedule);
+ enqueueInputs(node, todo);
+}
+
+void NodeScheduler::enqueueInputs(Node *node, NodeWorkList &todo)
+{
+ for (Node *input : node->inputs()) {
+ if (!input)
+ continue;
+ if (!needsScheduling(input))
+ continue;
+ if (!isLive(input))
+ continue;
+ auto sd = schedulerData(input);
+ if (sd->isFixed)
+ continue;
+ qCDebug(lcSched).noquote() << "... enqueueing input node" << input->id()
+ << todo.status(input);
+ if (sd->unscheduledUses != SchedulerData::NotYetCalculated) {
+ if (sd->unscheduledUses > 0)
+ --sd->unscheduledUses;
+ if (sd->unscheduledUses == 0)
+ todo.reEnqueue(input);
+ } else {
+ todo.reEnqueue(input);
+ }
+ }
+}
+
+Node *NodeScheduler::firstNotFixedUse(Node *node)
+{
+ for (Node *use : node->uses()) {
+ if (!isLive(use))
+ continue;
+ if (!schedulerData(use)->isFixed)
+ return use;
+ }
+ return nullptr;
+}
+
+MIBlock *NodeScheduler::commonDominatorOfUses(Node *node)
+{
+ MIBlock *commonDominator = nullptr;
+ for (auto useIt = node->uses().begin(), useEIt = node->uses().end(); useIt != useEIt; ++useIt) {
+ Node *use = *useIt;
+ if (!isLive(use))
+ continue;
+ // region nodes use other nodes through their control dependency. But those nodes should
+ // already have been placed as block terminators before.
+ Q_ASSERT(use->opcode() != Meta::Region);
+ if (use->opcode() == Meta::Phi || use->opcode() == Meta::EffectPhi) {
+ // find the predecessor block defining this input
+ Node *region = use->controlInput(0);
+ Node *input = region->controlInput(useIt.inputIndex());
+ use = input;
+ }
+ auto minBlock = schedulerData(use)->minimumBlock;
+ if (commonDominator == nullptr)
+ commonDominator = minBlock;
+ else
+ commonDominator = getCommonDominator(commonDominator, minBlock);
+ }
+ return commonDominator;
+}
+
+void NodeScheduler::scheduleNodesInBlock()
+{
+ auto startBlock = m_miFunction->block(MIFunction::StartBlockIndex);
+ for (Node *n : m_live.reachable()) {
+ auto sd = schedulerData(n);
+ if (!sd->minimumBlock)
+ sd->minimumBlock = startBlock;
+ }
+
+ std::vector<std::vector<SchedulerData *>> nodesForBlock;
+ nodesForBlock.resize(m_miFunction->blockCount());
+
+ for (auto sd : m_schedulerData) {
+ if (sd == nullptr)
+ continue;
+ if (!isLive(sd->node))
+ continue;
+ sd->unscheduledUses = 0;
+ for (Node *use : sd->node->uses()) {
+ if (!needsScheduling(use))
+ continue;
+ if (schedulerData(use)->isScheduledInBlock)
+ continue;
+ if (schedulerData(use)->minimumBlock == sd->minimumBlock)
+ ++sd->unscheduledUses;
+ }
+ if (sd->unscheduledUses == 0)
+ nodesForBlock[sd->minimumBlock->index()].push_back(sd);
+ }
+
+ NodeWorkList todo(m_irFunction->graph());
+ for (MIBlock *b : m_miFunction->blocks()) {
+ qCDebug(lcSched) << "Scheduling inside block" << b->index();
+ MIInstr *insertionPoint = &b->instructions().back();
+ todo.enqueue(insertionPoint->irNode());
+ scheduleNodesInBlock(insertionPoint, b, todo);
+ Q_ASSERT(todo.isEmpty());
+ for (auto sd : nodesForBlock[b->index()]) {
+ if (!sd->isScheduledInBlock)
+ todo.enqueue(sd->node);
+ }
+ scheduleNodesInBlock(insertionPoint, b, todo);
+ Q_ASSERT(todo.isEmpty());
+ todo.reset();
+ }
+}
+
+void NodeScheduler::scheduleNodesInBlock(MIInstr *&insertionPoint, MIBlock *b, NodeWorkList &todo)
+{
+ while (Node *n = todo.dequeueNextNodeForVisiting())
+ scheduleNodeInBlock(n, insertionPoint, b, todo);
+}
+
+void NodeScheduler::scheduleNodeInBlock(Node *node, MIInstr *&insertionPoint, MIBlock *b,
+ NodeWorkList &todo)
+{
+ Q_ASSERT(!node->isDead());
+
+ if (!isLive(node))
+ return;
+
+ if (!needsScheduling(node))
+ return;
+
+ auto nodeData = schedulerData(node);
+ if (nodeData->minimumBlock != b)
+ return;
+
+ const bool wasAlreadyScheduled = nodeData->isScheduledInBlock;
+ if (!wasAlreadyScheduled) {
+ if (nodeData->unscheduledUses)
+ return;
+
+ scheduleNodeNow(node, insertionPoint);
+ }
+
+ if (Node *framestate = node->frameStateInput())
+ scheduleNodeInBlock(framestate, insertionPoint, b, todo);
+
+ for (Node *input : node->inputs()) {
+ if (!input)
+ continue;
+ if (!needsScheduling(input))
+ continue;
+ if (!isLive(input))
+ continue;
+ auto inputInfo = schedulerData(input);
+ if (inputInfo->isScheduledInBlock)
+ continue;
+ Q_ASSERT(inputInfo->minimumBlock != nullptr);
+ if (inputInfo->minimumBlock != b)
+ continue;
+ Q_ASSERT(!input->isDead());
+ Q_ASSERT(inputInfo->unscheduledUses != SchedulerData::NotYetCalculated);
+ if (!wasAlreadyScheduled && inputInfo->unscheduledUses > 0)
+ --inputInfo->unscheduledUses;
+ if (inputInfo->unscheduledUses == 0)
+ todo.enqueue(input);
+ }
+}
+
+void NodeScheduler::scheduleNodeNow(Node *node, MIInstr *&insertionPoint)
+{
+ qCDebug(lcSched) << ".. scheduling node" << node->id()
+ << "in block" << insertionPoint->parent()->index()
+ << "before node" << insertionPoint->irNode()->id();
+
+ MIInstr *newInstr = createMIInstruction(node);
+ newInstr->insertBefore(insertionPoint);
+ insertionPoint = newInstr;
+}
+
+void NodeScheduler::place(Node *node, MIBlock *b)
+{
+ Q_ASSERT(!node->isDead());
+
+ if (b == nullptr)
+ return;
+
+ schedulerData(node)->minimumBlock = b;
+}
+
+void NodeScheduler::placeFixed(Node *node, MIBlock *b, ScheduleOrNot markScheduled)
+{
+ place(node, b);
+ auto sd = schedulerData(node);
+ Q_ASSERT(!sd->isFixed);
+ sd->isFixed = true;
+ sd->isScheduledInBlock = markScheduled == Schedule;
+}
+
+unsigned NodeScheduler::vregForNode(Node *node)
+{
+ unsigned &vreg = m_vregs[unsigned(node->id())];
+ if (vreg == std::numeric_limits<unsigned>::max())
+ vreg = m_nextVReg++;
+ return vreg;
+}
+
+void NodeScheduler::addBlockStart(std::vector<Node *> &roots, Node *startNode, MIBlock *block)
+{
+ block->instructions().insert(block->instructions().begin(), createMIInstruction(startNode));
+ if (startNode->opcode() == Meta::Region) {
+ for (Node *use : startNode->uses()) {
+ if (use->opcode() == Meta::Phi && isLive(use)) {
+ block->addArgument(MIOperand::createVirtualRegister(use, vregForNode(use)));
+ placeFixed(use, block, Schedule);
+ roots.push_back(use);
+ } else if (use->opcode() == Meta::EffectPhi && isLive(use)) {
+ placeFixed(use, block, Schedule);
+ roots.push_back(use);
+ }
+ }
+ }
+}
+
+void NodeScheduler::handleStartNode(Node *startNode, MIBlock *startBlock)
+{
+ startBlock->instructions().push_front(createMIInstruction(startNode));
+
+ QVarLengthArray<Node *, 32> args;
+ for (Node *use : startNode->uses()) {
+ switch (use->opcode()) {
+ case Meta::Engine: Q_FALLTHROUGH();
+ case Meta::CppFrame:
+ case Meta::Function:
+ placeFixed(use, startBlock, Schedule);
+ break;
+ case Meta::Parameter: {
+ auto param = ParameterPayload::get(*use->operation());
+ int idx = int(param->parameterIndex());
+ if (args.size() <= idx)
+ args.resize(idx + 1);
+ args[int(idx)] = use;
+ placeFixed(use, startBlock, Schedule);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ for (unsigned i = 0, ei = unsigned(args.size()); i != ei; ++i) {
+ if (Node *arg = args.at(int(i)))
+ startBlock->addArgument(MIOperand::createJSStackSlot(arg, i));
+ }
+}
+
+static Node *firstControlOutput(Node *n)
+{
+ for (auto it = n->uses().begin(), eit = n->uses().end(); it != eit; ++it) {
+ if (it.isUsedAsControl())
+ return *it;
+ }
+ return nullptr;
+}
+
+MIInstr *NodeScheduler::createMIInstruction(Node *node)
+{
+ const auto opcode = node->operation()->kind();
+
+ unsigned nArgs = 0;
+ switch (opcode) {
+ case Meta::UnwindDispatch: Q_FALLTHROUGH();
+ case Meta::Jump: {
+ Node *target = firstControlOutput(node);
+ if (target->opcode() == Meta::Region) {
+ for (Node *n : target->uses()) {
+ if (n->opcode() == Meta::Phi && isLive(n))
+ ++nArgs;
+ }
+ }
+ }
+ break;
+ case Meta::Branch:
+ nArgs = 1;
+ break;
+ case Meta::Return:
+ nArgs = 1;
+ break;
+ default:
+ nArgs = node->operation()->valueInputCount();
+ break;
+ }
+
+ MIInstr *instr = MIInstr::create(m_irFunction->pool(), node, nArgs);
+ for (unsigned i = 0, ei = node->operation()->valueInputCount(); i != ei; ++i)
+ instr->setOperand(i, createMIOperand(node->input(i)));
+ if (node->opcode() != Meta::Start && node->operation()->valueOutputCount() > 0)
+ instr->setDestination(createMIOperand(node));
+
+ schedulerData(node)->isScheduledInBlock = true;
+ return instr;
+}
+
+MIOperand NodeScheduler::createMIOperand(Node *node)
+{
+ if (node->operation()->isConstant())
+ return MIOperand::createConstant(node);
+
+ auto opcode = node->operation()->kind();
+ switch (opcode) {
+ case Meta::Parameter:
+ return MIOperand::createJSStackSlot(
+ node, unsigned(ParameterPayload::get(*node->operation())->parameterIndex()));
+ case Meta::Engine:
+ return MIOperand::createEngineRegister(node);
+ case Meta::CppFrame:
+ return MIOperand::createCppFrameRegister(node);
+ case Meta::Function:
+ return MIOperand::createFunction(node);
+ default:
+ if ((node->opcode() == Meta::Call
+ && CallPayload::get(*node->operation())->callee() == Meta::JSThisToObject)
+ || node->opcode() == Meta::StoreThis) {
+ return MIOperand::createJSStackSlot(node, CallData::This);
+ } else {
+ return MIOperand::createVirtualRegister(node, vregForNode(node));
+ }
+ }
+}
+
+void NodeScheduler::showNodesByBlock(const QString &description) const
+{
+ if (!lcSched().isDebugEnabled())
+ return;
+
+ qCDebug(lcSched) << description;
+
+ for (MIBlock *b : m_miFunction->blocks()) {
+ QString s;
+ for (const SchedulerData *sd : m_schedulerData) {
+ if (!sd)
+ continue;
+ if (!isLive(sd->node))
+ continue;
+ if (sd->minimumBlock == b) {
+ if (!s.isEmpty())
+ s += QStringLiteral(", ");
+ s += QStringLiteral("%1 (%2)").arg(QString::number(sd->node->id()),
+ sd->node->operation()->debugString());
+ }
+ }
+ if (s.isEmpty())
+ s = QStringLiteral("<<none>>");
+ qCDebug(lcSched, "Nodes in block %u: %s", b->index(), s.toUtf8().constData());
+ }
+}
+
+void NodeScheduler::dumpDotCFG() const
+{
+ QString out;
+ out += QLatin1Char('\n');
+ out += QStringLiteral("digraph{root=\"L%1\" label=\"Control Flow Graph\";"
+ "node[shape=circle];edge[dir=forward fontsize=10]\n")
+ .arg(MIFunction::StartBlockIndex);
+ for (MIBlock *src : m_miFunction->blocks()) {
+ for (MIBlock *dst : src->outEdges()) {
+ out += QStringLiteral("L%1->L%2\n").arg(QString::number(src->index()),
+ QString::number(dst->index()));
+ }
+ }
+ out += QStringLiteral("}\n");
+ qCDebug(lcDotCFG).nospace().noquote() << out;
+}
+
+} // IR namespace
+} // QV4 namespace
+QT_END_NAMESPACE