/**************************************************************************** ** ** Copyright (C) 2016 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$ ** ****************************************************************************/ // When building with debug code, the macro below will enable debug helpers when using libc++. // For example, the std::vector::operator[] will use _LIBCPP_ASSERT to check if the index is // within the array bounds. Note that this only works reliably with OSX 10.9 or later. //#define _LIBCPP_DEBUG2 2 #include "qv4ssa_p.h" #include "qv4isel_util_p.h" #include "qv4util_p.h" #include #include #include #include #include #include #include #include #include #include QT_USE_NAMESPACE using namespace QV4; using namespace IR; namespace { enum { DebugMoveMapping = 0 }; #ifdef QT_NO_DEBUG enum { DoVerification = 0 }; #else enum { DoVerification = 1 }; #endif static void showMeTheCode(IR::Function *function, const char *marker) { static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_IR"); if (showCode) { qDebug() << marker; QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream stream(&buf); IRPrinter(&stream).print(function); stream << endl; qDebug("%s", buf.data().constData()); } } class ProcessedBlocks { BitVector processed; public: ProcessedBlocks(IR::Function *function) : processed(function->basicBlockCount(), false) {} bool alreadyProcessed(BasicBlock *bb) const { Q_ASSERT(bb); return processed.at(bb->index()); } void markAsProcessed(BasicBlock *bb) { processed.setBit(bb->index()); } }; class BasicBlockSet { typedef BitVector Flags; QVarLengthArray blockNumbers; Flags *blockFlags; IR::Function *function; enum { MaxVectorCapacity = 8 }; public: class const_iterator { const BasicBlockSet &set; // ### These two members could go into a union, but clang won't compile (https://codereview.qt-project.org/#change,74259) QVarLengthArray::const_iterator numberIt; int flagIt; friend class BasicBlockSet; const_iterator(const BasicBlockSet &set, bool end) : set(set) { if (end || !set.function) { if (!set.blockFlags) numberIt = set.blockNumbers.end(); else flagIt = set.blockFlags->size(); } else { if (!set.blockFlags) numberIt = set.blockNumbers.begin(); else findNextWithFlags(0); } } void findNextWithFlags(int start) { flagIt = set.blockFlags->findNext(start, true, /*wrapAround = */false); Q_ASSERT(flagIt <= set.blockFlags->size()); } public: BasicBlock *operator*() const { if (!set.blockFlags) { return set.function->basicBlock(*numberIt); } else { Q_ASSERT(flagIt <= set.function->basicBlockCount()); return set.function->basicBlock(flagIt); } } bool operator==(const const_iterator &other) const { if (&set != &other.set) return false; if (!set.blockFlags) return numberIt == other.numberIt; else return flagIt == other.flagIt; } bool operator!=(const const_iterator &other) const { return !(*this == other); } const_iterator &operator++() { if (!set.blockFlags) ++numberIt; else findNextWithFlags(flagIt + 1); return *this; } }; friend class const_iterator; public: BasicBlockSet(IR::Function *f = 0): blockFlags(0), function(0) { if (f) init(f); } #ifdef Q_COMPILER_RVALUE_REFS BasicBlockSet(BasicBlockSet &&other): blockFlags(0) { std::swap(blockNumbers, other.blockNumbers); std::swap(blockFlags, other.blockFlags); std::swap(function, other.function); } #endif // Q_COMPILER_RVALUE_REFS BasicBlockSet(const BasicBlockSet &other) : blockFlags(0) , function(other.function) { if (other.blockFlags) blockFlags = new Flags(*other.blockFlags); blockNumbers = other.blockNumbers; } BasicBlockSet &operator=(const BasicBlockSet &other) { if (blockFlags) { delete blockFlags; blockFlags = 0; } function = other.function; if (other.blockFlags) blockFlags = new Flags(*other.blockFlags); blockNumbers = other.blockNumbers; return *this; } ~BasicBlockSet() { delete blockFlags; } void init(IR::Function *f) { Q_ASSERT(!function); Q_ASSERT(f); function = f; } bool empty() const { return begin() == end(); } void insert(BasicBlock *bb) { Q_ASSERT(function); if (blockFlags) { blockFlags->setBit(bb->index()); return; } for (int i = 0; i < blockNumbers.size(); ++i) { if (blockNumbers[i] == bb->index()) return; } if (blockNumbers.size() == MaxVectorCapacity) { blockFlags = new Flags(function->basicBlockCount(), false); for (int i = 0; i < blockNumbers.size(); ++i) { blockFlags->setBit(blockNumbers[i]); } blockNumbers.clear(); blockFlags->setBit(bb->index()); } else { blockNumbers.append(bb->index()); } } void remove(BasicBlock *bb) { Q_ASSERT(function); if (blockFlags) { blockFlags->clearBit(bb->index()); return; } for (int i = 0; i < blockNumbers.size(); ++i) { if (blockNumbers[i] == bb->index()) { blockNumbers.remove(i); return; } } } const_iterator begin() const { return const_iterator(*this, false); } const_iterator end() const { return const_iterator(*this, true); } void collectValues(std::vector &bbs) const { Q_ASSERT(function); for (const_iterator it = begin(), eit = end(); it != eit; ++it) bbs.push_back(*it); } bool contains(BasicBlock *bb) const { Q_ASSERT(function); if (blockFlags) return blockFlags->at(bb->index()); for (int i = 0; i < blockNumbers.size(); ++i) { if (blockNumbers[i] == bb->index()) return true; } return false; } }; class DominatorTree { enum { DebugDominatorFrontiers = 0, DebugImmediateDominators = 0, DebugCodeCanUseLotsOfCpu = 0 }; typedef int BasicBlockIndex; enum { InvalidBasicBlockIndex = -1 }; struct Data { int N; std::vector dfnum; // BasicBlock index -> dfnum std::vector vertex; std::vector parent; // BasicBlock index -> parent BasicBlock index std::vector ancestor; // BasicBlock index -> ancestor BasicBlock index std::vector best; // BasicBlock index -> best BasicBlock index std::vector semi; // BasicBlock index -> semi dominator BasicBlock index std::vector samedom; // BasicBlock index -> same dominator BasicBlock index Data(): N(0) {} }; IR::Function *function; QScopedPointer d; std::vector idom; // BasicBlock index -> immediate dominator BasicBlock index std::vector DF; // BasicBlock index -> dominator frontier struct DFSTodo { BasicBlockIndex node, parent; DFSTodo() : node(InvalidBasicBlockIndex) , parent(InvalidBasicBlockIndex) {} DFSTodo(BasicBlockIndex node, BasicBlockIndex parent) : node(node) , parent(parent) {} }; void DFS(BasicBlockIndex node) { std::vector worklist; worklist.reserve(d->vertex.capacity() / 2); DFSTodo todo(node, InvalidBasicBlockIndex); while (true) { BasicBlockIndex n = todo.node; if (d->dfnum[n] == 0) { d->dfnum[n] = d->N; d->vertex[d->N] = n; d->parent[n] = todo.parent; ++d->N; const BasicBlock::OutgoingEdges &out = function->basicBlock(n)->out; for (int i = out.size() - 1; i > 0; --i) worklist.push_back(DFSTodo(out[i]->index(), n)); if (out.size() > 0) { todo.node = out.first()->index(); todo.parent = n; continue; } } if (worklist.empty()) break; todo = worklist.back(); worklist.pop_back(); } } BasicBlockIndex ancestorWithLowestSemi(BasicBlockIndex v, std::vector &worklist) { worklist.clear(); for (BasicBlockIndex it = v; it != InvalidBasicBlockIndex; it = d->ancestor[it]) worklist.push_back(it); if (worklist.size() < 2) return d->best[v]; BasicBlockIndex b = InvalidBasicBlockIndex; BasicBlockIndex last = worklist.back(); Q_ASSERT(worklist.size() <= INT_MAX); for (int it = static_cast(worklist.size()) - 2; it >= 0; --it) { BasicBlockIndex bbIt = worklist[it]; d->ancestor[bbIt] = last; BasicBlockIndex &best_it = d->best[bbIt]; if (b != InvalidBasicBlockIndex && d->dfnum[d->semi[b]] < d->dfnum[d->semi[best_it]]) best_it = b; else b = best_it; } return b; } void link(BasicBlockIndex p, BasicBlockIndex n) { d->ancestor[n] = p; d->best[n] = n; } void calculateIDoms() { Q_ASSERT(function->basicBlock(0)->in.isEmpty()); const int bbCount = function->basicBlockCount(); d->vertex = std::vector(bbCount, InvalidBasicBlockIndex); d->parent = std::vector(bbCount, InvalidBasicBlockIndex); d->dfnum = std::vector(size_t(bbCount), 0); d->semi = std::vector(bbCount, InvalidBasicBlockIndex); d->ancestor = std::vector(bbCount, InvalidBasicBlockIndex); idom = std::vector(bbCount, InvalidBasicBlockIndex); d->samedom = std::vector(bbCount, InvalidBasicBlockIndex); d->best = std::vector(bbCount, InvalidBasicBlockIndex); QHash > bucket; bucket.reserve(bbCount); DFS(function->basicBlock(0)->index()); Q_ASSERT(d->N == function->liveBasicBlocksCount()); std::vector worklist; worklist.reserve(d->vertex.capacity() / 2); for (int i = d->N - 1; i > 0; --i) { BasicBlockIndex n = d->vertex[i]; BasicBlockIndex p = d->parent[n]; BasicBlockIndex s = p; for (BasicBlock *v : function->basicBlock(n)->in) { BasicBlockIndex ss = InvalidBasicBlockIndex; if (d->dfnum[v->index()] <= d->dfnum[n]) ss = v->index(); else ss = d->semi[ancestorWithLowestSemi(v->index(), worklist)]; if (d->dfnum[ss] < d->dfnum[s]) s = ss; } d->semi[n] = s; bucket[s].push_back(n); link(p, n); if (bucket.contains(p)) { for (BasicBlockIndex v : bucket[p]) { BasicBlockIndex y = ancestorWithLowestSemi(v, worklist); BasicBlockIndex semi_v = d->semi[v]; if (d->semi[y] == semi_v) idom[v] = semi_v; else d->samedom[v] = y; } bucket.remove(p); } } for (int i = 1; i < d->N; ++i) { BasicBlockIndex n = d->vertex[i]; Q_ASSERT(n != InvalidBasicBlockIndex); Q_ASSERT(!bucket.contains(n)); Q_ASSERT(d->ancestor[n] != InvalidBasicBlockIndex && ((d->semi[n] != InvalidBasicBlockIndex && d->dfnum[d->ancestor[n]] <= d->dfnum[d->semi[n]]) || d->semi[n] == n)); BasicBlockIndex sdn = d->samedom[n]; if (sdn != InvalidBasicBlockIndex) idom[n] = idom[sdn]; } dumpImmediateDominators(); } struct NodeProgress { std::vector children; std::vector todo; }; public: DominatorTree(IR::Function *function) : function(function) , d(new Data) { calculateIDoms(); d.reset(); } void computeDF() { DF.resize(function->basicBlockCount()); // compute children of each node in the dominator tree std::vector > children; // BasicBlock index -> children children.resize(function->basicBlockCount()); for (BasicBlock *n : function->basicBlocks()) { if (n->isRemoved()) continue; const BasicBlockIndex nodeIndex = n->index(); Q_ASSERT(function->basicBlock(nodeIndex) == n); const BasicBlockIndex nodeDominator = idom[nodeIndex]; if (nodeDominator == InvalidBasicBlockIndex) continue; // there is no dominator to add this node to as a child (e.g. the start node) children[nodeDominator].push_back(nodeIndex); } // Fill the worklist and initialize the node status for each basic-block std::vector nodeStatus; nodeStatus.resize(function->basicBlockCount()); std::vector worklist; worklist.reserve(function->basicBlockCount()); for (BasicBlock *bb : function->basicBlocks()) { if (bb->isRemoved()) continue; BasicBlockIndex nodeIndex = bb->index(); worklist.push_back(nodeIndex); NodeProgress &np = nodeStatus[nodeIndex]; np.children = children[nodeIndex]; np.todo = children[nodeIndex]; } BitVector DF_done(function->basicBlockCount(), false); while (!worklist.empty()) { BasicBlockIndex node = worklist.back(); if (DF_done.at(node)) { worklist.pop_back(); continue; } NodeProgress &np = nodeStatus[node]; std::vector::iterator it = np.todo.begin(); while (it != np.todo.end()) { if (DF_done.at(*it)) { it = np.todo.erase(it); } else { worklist.push_back(*it); break; } } if (np.todo.empty()) { BasicBlockSet &S = DF[node]; S.init(function); for (BasicBlock *y : function->basicBlock(node)->out) if (idom[y->index()] != node) S.insert(y); for (BasicBlockIndex child : np.children) { const BasicBlockSet &ws = DF[child]; for (BasicBlockSet::const_iterator it = ws.begin(), eit = ws.end(); it != eit; ++it) { BasicBlock *w = *it; const BasicBlockIndex wIndex = w->index(); if (node == wIndex || !dominates(node, w->index())) S.insert(w); } } DF_done.setBit(node); worklist.pop_back(); } } if (DebugDominatorFrontiers) { QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream qout(&buf); qout << "Dominator Frontiers:" << endl; for (BasicBlock *n : function->basicBlocks()) { if (n->isRemoved()) continue; qout << "\tDF[" << n->index() << "]: {"; const BasicBlockSet &SList = DF[n->index()]; for (BasicBlockSet::const_iterator i = SList.begin(), ei = SList.end(); i != ei; ++i) { if (i != SList.begin()) qout << ", "; qout << (*i)->index(); } qout << "}" << endl; } qDebug("%s", buf.data().constData()); } if (DebugDominatorFrontiers && DebugCodeCanUseLotsOfCpu) { for (BasicBlock *n : function->basicBlocks()) { if (n->isRemoved()) continue; const BasicBlockSet &fBlocks = DF[n->index()]; for (BasicBlockSet::const_iterator it = fBlocks.begin(), eit = fBlocks.end(); it != eit; ++it) { BasicBlock *fBlock = *it; Q_ASSERT(!dominates(n, fBlock) || fBlock == n); bool hasDominatedSucc = false; for (BasicBlock *succ : fBlock->in) { if (dominates(n, succ)) { hasDominatedSucc = true; break; } } if (!hasDominatedSucc) { qDebug("%d in DF[%d] has no dominated predecessors", fBlock->index(), n->index()); } Q_ASSERT(hasDominatedSucc); } } } } const BasicBlockSet &dominatorFrontier(BasicBlock *n) const { return DF[n->index()]; } BasicBlock *immediateDominator(BasicBlock *bb) const { const BasicBlockIndex idx = idom[bb->index()]; if (idx == -1) return 0; return function->basicBlock(idx); } void dumpImmediateDominators() const { if (DebugImmediateDominators) { QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream qout(&buf); qout << "Immediate dominators:" << endl; for (BasicBlock *to : function->basicBlocks()) { if (to->isRemoved()) continue; qout << '\t'; BasicBlockIndex from = idom.at(to->index()); if (from != InvalidBasicBlockIndex) qout << from; else qout << "(none)"; qout << " dominates " << to->index() << endl; } qDebug("%s", buf.data().constData()); } } void setImmediateDominator(BasicBlock *bb, BasicBlock *newDominator) { Q_ASSERT(bb->index() >= 0); Q_ASSERT(!newDominator || newDominator->index() >= 0); if (static_cast::size_type>(bb->index()) >= idom.size()) { // This is a new block, probably introduced by edge splitting. So, we'll have to grow // the array before inserting the immediate dominator. idom.resize(function->basicBlockCount(), InvalidBasicBlockIndex); } const BasicBlockIndex newIdx = newDominator ? newDominator->index() : InvalidBasicBlockIndex; if (DebugImmediateDominators) qDebug() << "Setting idom of" << bb->index() << "from" << idom[bb->index()] << "to" << newIdx; idom[bb->index()] = newIdx; } void collectSiblings(BasicBlock *node, BasicBlockSet &siblings) { siblings.insert(node); const BasicBlockIndex dominator = idom[node->index()]; if (dominator == InvalidBasicBlockIndex) return; for (size_t i = 0, ei = idom.size(); i != ei; ++i) { if (idom[i] == dominator) { BasicBlock *bb = function->basicBlock(int(i)); if (!bb->isRemoved()) siblings.insert(bb); } } } void recalculateIDoms(const BasicBlockSet &nodes, BasicBlock *limit = 0) { const BasicBlockIndex limitIndex = limit ? limit->index() : InvalidBasicBlockIndex; BasicBlockSet todo(nodes), postponed(function); while (!todo.empty()) recalculateIDom(*todo.begin(), todo, postponed, limitIndex); } bool dominates(BasicBlock *dominator, BasicBlock *dominated) const { return dominates(dominator->index(), dominated->index()); } struct Cmp { std::vector *nodeDepths; Cmp(std::vector *nodeDepths) : nodeDepths(nodeDepths) { Q_ASSERT(nodeDepths); } bool operator()(BasicBlock *one, BasicBlock *two) const { if (one->isRemoved()) return false; if (two->isRemoved()) return true; return nodeDepths->at(one->index()) > nodeDepths->at(two->index()); } }; // Calculate a depth-first iteration order on the nodes of the dominator tree. // // The order of the nodes in the vector is not the same as one where a recursive depth-first // iteration is done on a tree. Rather, the nodes are (reverse) sorted on tree depth. // So for the: // 1 dominates 2 // 2 dominates 3 // 3 dominates 4 // 2 dominates 5 // the order will be: // 4, 3, 5, 2, 1 // or: // 4, 5, 3, 2, 1 // So the order of nodes on the same depth is undefined, but it will be after the nodes // they dominate, and before the nodes that dominate them. // // The reason for this order is that a proper DFS pre-/post-order would require inverting // the idom vector by either building a real tree datastructure or by searching the idoms // for siblings and children. Both have a higher time complexity than sorting by depth. QVector calculateDFNodeIterOrder() const { std::vector depths = calculateNodeDepths(); QVector order = function->basicBlocks(); std::sort(order.begin(), order.end(), Cmp(&depths)); for (int i = 0; i < order.size(); ) { if (order[i]->isRemoved()) order.remove(i); else ++i; } return order; } void mergeIntoPredecessor(BasicBlock *successor) { int succIdx = successor->index(); if (succIdx == InvalidBasicBlockIndex) { return; } int succDom = idom[unsigned(succIdx)]; for (BasicBlockIndex &idx : idom) { if (idx == succIdx) { idx = succDom; } } } private: bool dominates(BasicBlockIndex dominator, BasicBlockIndex dominated) const { // dominator can be Invalid when the dominated block has no dominator (i.e. the start node) Q_ASSERT(dominated != InvalidBasicBlockIndex); if (dominator == dominated) return false; for (BasicBlockIndex it = idom[dominated]; it != InvalidBasicBlockIndex; it = idom[it]) { if (it == dominator) return true; } return false; } // Algorithm: // - for each node: // - get the depth of a node. If it's unknown (-1): // - get the depth of the immediate dominator. // - if that's unknown too, calculate it by calling calculateNodeDepth // - set the current node's depth to that of immediate dominator + 1 std::vector calculateNodeDepths() const { std::vector nodeDepths(size_t(function->basicBlockCount()), -1); nodeDepths[0] = 0; for (BasicBlock *bb : function->basicBlocks()) { if (bb->isRemoved()) continue; int &bbDepth = nodeDepths[bb->index()]; if (bbDepth == -1) { const int immDom = idom[bb->index()]; int immDomDepth = nodeDepths[immDom]; if (immDomDepth == -1) immDomDepth = calculateNodeDepth(immDom, nodeDepths); bbDepth = immDomDepth + 1; } } return nodeDepths; } // Algorithm: // - search for the first dominator of a node that has a known depth. As all nodes are // reachable from the start node, and that node's depth is 0, this is finite. // - while doing that search, put all unknown nodes in the worklist // - pop all nodes from the worklist, and set their depth to the previous' (== dominating) // node's depth + 1 // This way every node's depth is calculated once, and the complexity is O(n). int calculateNodeDepth(int nodeIdx, std::vector &nodeDepths) const { std::vector worklist; worklist.reserve(8); int depth = -1; do { worklist.push_back(nodeIdx); nodeIdx = idom[nodeIdx]; depth = nodeDepths[nodeIdx]; } while (depth == -1); for (std::vector::const_reverse_iterator it = worklist.rbegin(), eit = worklist.rend(); it != eit; ++it) nodeDepths[*it] = ++depth; return depth; } // The immediate-dominator recalculation is used when edges are removed from the CFG. See // [Ramalingam] for a description. Note that instead of calculating the priority, a recursive // algorithm is used: when recalculating the immediate dominator of a node by looking for the // least-common ancestor, and a node is hit that also needs recalculation, a recursive call // is done to calculate that nodes immediate dominator first. // // Note that this simplified algorithm cannot cope with back-edges. It only works for // non-looping edges (which is our use-case). void recalculateIDom(BasicBlock *node, BasicBlockSet &todo, BasicBlockSet &postponed, BasicBlockIndex limit) { Q_ASSERT(!postponed.contains(node)); Q_ASSERT(todo.contains(node)); todo.remove(node); if (node->in.size() == 1) { // Special case: if the node has only one incoming edge, then that is the immediate // dominator. setImmediateDominator(node, node->in.first()); return; } std::vector prefix; prefix.reserve(32); for (BasicBlock *in : node->in) { if (node == in) // back-edge to self continue; if (dominates(node->index(), in->index())) // a known back-edge continue; if (prefix.empty()) { calculatePrefix(node, in, prefix, todo, postponed, limit); if (!prefix.empty()) { std::reverse(prefix.begin(), prefix.end()); Q_ASSERT(!prefix.empty()); Q_ASSERT(prefix.front() == limit || limit == InvalidBasicBlockIndex); } } else { std::vector anotherPrefix; anotherPrefix.reserve(prefix.size()); calculatePrefix(node, in, anotherPrefix, todo, postponed, limit); if (!anotherPrefix.empty()) commonPrefix(prefix, anotherPrefix); } } Q_ASSERT(!prefix.empty()); idom[node->index()] = prefix.back(); } void calculatePrefix(BasicBlock *node, BasicBlock *in, std::vector &prefix, BasicBlockSet &todo, BasicBlockSet &postponed, BasicBlockIndex limit) { for (BasicBlockIndex it = in->index(); it != InvalidBasicBlockIndex; it = idom[it]) { prefix.push_back(it); if (it == limit) return; BasicBlock *n = function->basicBlock(it); if (postponed.contains(n)) { // possible back-edge, bail out. prefix.clear(); return; } if (todo.contains(n)) { postponed.insert(node); recalculateIDom(n, todo, postponed, limit); postponed.remove(node); } } } // Calculate the LCA (Least Common Ancestor) by finding the longest common prefix between two // dominator chains. Note that "anotherPrefix" has the node's immediate dominator first, while // "bestPrefix" has it last (meaning: is in reverse order). The reason for this is that removing // nodes from "bestPrefix" is cheaper because it's done at the end of the vector, while // reversing all "anotherPrefix" nodes would take unnecessary time. static void commonPrefix(std::vector &bestPrefix, const std::vector &anotherPrefix) { const size_t anotherSize = anotherPrefix.size(); size_t minLen = qMin(bestPrefix.size(), anotherPrefix.size()); while (minLen != 0) { --minLen; if (bestPrefix[minLen] == anotherPrefix[anotherSize - minLen - 1]) { ++minLen; break; } } if (minLen != bestPrefix.size()) bestPrefix.erase(bestPrefix.begin() + minLen, bestPrefix.end()); } }; class VariableCollector { std::vector _allTemps; std::vector _defsites; std::vector > A_orig; BitVector nonLocals; BitVector killed; BasicBlock *currentBB; bool isCollectable(Temp *t) const { Q_UNUSED(t); Q_ASSERT(t->kind != Temp::PhysicalRegister && t->kind != Temp::StackSlot); return true; } void addDefInCurrentBlock(Temp *t) { std::vector &temps = A_orig[currentBB->index()]; if (std::find(temps.begin(), temps.end(), t->index) == temps.end()) temps.push_back(t->index); } void addTemp(Temp *t) { if (_allTemps[t->index].kind == Temp::Invalid) _allTemps[t->index] = *t; } public: VariableCollector(IR::Function *function) { _allTemps.resize(function->tempCount); _defsites.resize(function->tempCount); for (int i = 0; i < function->tempCount; ++i) _defsites[i].init(function); nonLocals.resize(function->tempCount); const size_t ei = function->basicBlockCount(); A_orig.resize(ei); for (size_t i = 0; i != ei; ++i) A_orig[i].reserve(8); for (BasicBlock *bb : function->basicBlocks()) { if (bb->isRemoved()) continue; currentBB = bb; killed.assign(function->tempCount, false); for (Stmt *s : bb->statements()) visit(s); } } const std::vector &allTemps() const { return _allTemps; } void collectDefSites(const Temp &n, std::vector &bbs) const { Q_ASSERT(!n.isInvalid()); Q_ASSERT(n.index < _defsites.size()); _defsites[n.index].collectValues(bbs); } const std::vector &inBlock(BasicBlock *n) const { return A_orig.at(n->index()); } bool isNonLocal(const Temp &var) const { Q_ASSERT(!var.isInvalid()); Q_ASSERT(static_cast(var.index) < nonLocals.size()); return nonLocals.at(var.index); } private: void visit(Stmt *s) { if (s->asPhi()) { // nothing to do } else if (auto move = s->asMove()) { visit(move->source); if (Temp *t = move->target->asTemp()) { addTemp(t); if (isCollectable(t)) { _defsites[t->index].insert(currentBB); addDefInCurrentBlock(t); // For semi-pruned SSA: killed.setBit(t->index); } } else { visit(move->target); } } else { STMT_VISIT_ALL_KINDS(s) } } void visit(Expr *e) { if (auto t = e->asTemp()) { addTemp(t); if (isCollectable(t)) { if (!killed.at(t->index)) { nonLocals.setBit(t->index); } } } else { EXPR_VISIT_ALL_KINDS(e); } } }; struct UntypedTemp { Temp temp; UntypedTemp() {} UntypedTemp(const Temp &t): temp(t) {} }; inline bool operator==(const UntypedTemp &t1, const UntypedTemp &t2) Q_DECL_NOTHROW { return t1.temp.index == t2.temp.index && t1.temp.kind == t2.temp.kind; } inline bool operator!=(const UntypedTemp &t1, const UntypedTemp &t2) Q_DECL_NOTHROW { return !(t1 == t2); } class DefUses { public: struct DefUse { DefUse() : defStmt(0) , blockOfStatement(0) { uses.reserve(8); } Temp temp; Stmt *defStmt; BasicBlock *blockOfStatement; QVector uses; bool isValid() const { return temp.kind != Temp::Invalid; } void clear() { defStmt = 0; blockOfStatement = 0; uses.clear(); } }; private: std::vector _defUses; typedef QVarLengthArray Temps; std::vector _usesPerStatement; void ensure(Temp *newTemp) { if (_defUses.size() <= newTemp->index) { _defUses.resize(newTemp->index + 1); } } void ensure(Stmt *s) { Q_ASSERT(s->id() >= 0); if (static_cast(s->id()) >= _usesPerStatement.size()) { _usesPerStatement.resize(s->id() + 1); } } void addUseForStatement(Stmt *s, const Temp &var) { ensure(s); _usesPerStatement[s->id()].push_back(var); } public: DefUses(IR::Function *function) { _usesPerStatement.resize(function->statementCount()); _defUses.resize(function->tempCount); } void cleanup() { for (size_t i = 0, ei = _defUses.size(); i != ei; ++i) { DefUse &defUse = _defUses[i]; if (defUse.isValid() && !defUse.defStmt) defUse.clear(); } } unsigned statementCount() const { return unsigned(_usesPerStatement.size()); } unsigned tempCount() const { return unsigned(_defUses.size()); } const Temp &temp(int idx) const { return _defUses[idx].temp; } void addDef(Temp *newTemp, Stmt *defStmt, BasicBlock *defBlock) { ensure(newTemp); DefUse &defUse = _defUses[newTemp->index]; Q_ASSERT(!defUse.isValid()); defUse.temp = *newTemp; defUse.defStmt = defStmt; defUse.blockOfStatement = defBlock; } QVector defsUntyped() const { QVector res; res.reserve(tempCount()); for (const DefUse &du : _defUses) if (du.isValid()) res.append(UntypedTemp(du.temp)); return res; } std::vector defs() const { std::vector res; const size_t ei = _defUses.size(); res.reserve(ei); for (size_t i = 0; i != ei; ++i) { const DefUse &du = _defUses.at(i); if (du.isValid()) res.push_back(&du.temp); } return res; } void removeDef(const Temp &variable) { Q_ASSERT(static_cast(variable.index) < _defUses.size()); _defUses[variable.index].clear(); } void addUses(const Temp &variable, const QVector &newUses) { Q_ASSERT(static_cast(variable.index) < _defUses.size()); QVector &uses = _defUses[variable.index].uses; for (Stmt *stmt : newUses) if (std::find(uses.begin(), uses.end(), stmt) == uses.end()) uses.push_back(stmt); } void addUse(const Temp &variable, Stmt *newUse) { if (_defUses.size() <= variable.index) { _defUses.resize(variable.index + 1); DefUse &du = _defUses[variable.index]; du.temp = variable; du.uses.push_back(newUse); addUseForStatement(newUse, variable); return; } QVector &uses = _defUses[variable.index].uses; if (std::find(uses.begin(), uses.end(), newUse) == uses.end()) uses.push_back(newUse); addUseForStatement(newUse, variable); } int useCount(const Temp &variable) const { Q_ASSERT(static_cast(variable.index) < _defUses.size()); return _defUses[variable.index].uses.size(); } Stmt *defStmt(const Temp &variable) const { Q_ASSERT(static_cast(variable.index) < _defUses.size()); return _defUses[variable.index].defStmt; } BasicBlock *defStmtBlock(const Temp &variable) const { Q_ASSERT(static_cast(variable.index) < _defUses.size()); return _defUses[variable.index].blockOfStatement; } void replaceBasicBlock(BasicBlock *from, BasicBlock *to) { for (auto &du : _defUses) { if (du.blockOfStatement == from) { du.blockOfStatement = to; } } } void removeUse(Stmt *usingStmt, const Temp &var) { Q_ASSERT(static_cast(var.index) < _defUses.size()); QVector &uses = _defUses[var.index].uses; uses.erase(std::remove(uses.begin(), uses.end(), usingStmt), uses.end()); } void registerNewStatement(Stmt *s) { ensure(s); } const Temps &usedVars(Stmt *s) const { Q_ASSERT(s->id() >= 0); Q_ASSERT(static_cast(s->id()) < _usesPerStatement.size()); return _usesPerStatement[s->id()]; } const QVector &uses(const Temp &var) const { return _defUses[var.index].uses; } QVector removeDefUses(Stmt *s) { QVector defStmts; for (const Temp &usedVar : usedVars(s)) { if (Stmt *ds = defStmt(usedVar)) defStmts += ds; removeUse(s, usedVar); } if (Move *m = s->asMove()) { if (Temp *t = m->target->asTemp()) removeDef(*t); } else if (Phi *p = s->asPhi()) { removeDef(*p->targetTemp); } return defStmts; } void dump() const { QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream qout(&buf); qout << "Defines and uses:" << endl; for (const DefUse &du : _defUses) { if (!du.isValid()) continue; qout << '%' << du.temp.index; qout << " -> defined in block " << du.blockOfStatement->index() << ", statement: " << du.defStmt->id() << endl; qout << " uses:"; for (Stmt *s : du.uses) qout << ' ' << s->id(); qout << endl; } qout << "Uses per statement:" << endl; for (size_t i = 0, ei = _usesPerStatement.size(); i != ei; ++i) { qout << " " << i << ":"; for (const Temp &t : _usesPerStatement[i]) qout << ' ' << t.index; qout << endl; } qDebug("%s", buf.data().constData()); } }; void insertPhiNode(const Temp &a, BasicBlock *y, IR::Function *f) { Phi *phiNode = f->NewStmt(); phiNode->targetTemp = f->New(); phiNode->targetTemp->init(a.kind, a.index); y->prependStatement(phiNode); phiNode->incoming.resize(y->in.size()); for (int i = 0, ei = y->in.size(); i < ei; ++i) { Temp *t = f->New(); t->init(a.kind, a.index); phiNode->incoming[i] = t; } } // High-level (recursive) algorithm: // Mapping: old temp number -> new temp number // // Start: // Rename(start-node) // // Rename(node, mapping): // for each statement S in block n // if S not in a phi-function // for each use of some variable x in S // y = mapping[x] // replace the use of x with y in S // for each definition of some variable a in S [1] // a_new = generate new/unique temp // mapping[a] = a_new // replace definition of a with definition of a_new in S // for each successor Y of block n // Suppose n is the j-th predecessor of Y // for each phi function in Y // suppose the j-th operand of the phi-function is a // i = mapping[a] // replace the j-th operand with a_i // for each child X of n [2] // Rename(X) // for each newly generated temp from step [1] restore the old value [3] // // This algorithm can run out of CPU stack space when there are lots of basic-blocks, like in a // switch statement with 8000 cases that all fall-through. The iterativer version below uses a // work-item stack, where step [1] from the algorithm above also pushes an "undo mapping change", // and step [2] pushes a "rename(X)" action. This eliminates step [3]. // // Iterative version: // Mapping: old temp number -> new temp number // // The stack can hold two kinds of actions: // "Rename basic block n" // "Restore count for temp" // // Start: // counter = 0 // push "Rename start node" onto the stack // while the stack is not empty: // take the last item, and process it // // Rename(n) = // for each statement S in block n // if S not in a phi-function // for each use of some variable x in S // y = mapping[x] // replace the use of x with y in S // for each definition of some variable a in S // old = mapping[a] // push Undo(a, old) // counter = counter + 1 // new = counter; // mapping[a] = new // replace definition of a with definition of a_new in S // for each successor Y of block n // Suppose n is the j-th predecessor of Y // for each phi function in Y // suppose the j-th operand of the phi-function is a // i = mapping[a] // replace the j-th operand with a_i // for each child X of n // push Rename(X) // // Undo(t, c) = // mapping[t] = c class VariableRenamer { Q_DISABLE_COPY(VariableRenamer) IR::Function *function; DefUses &defUses; unsigned tempCount; typedef std::vector Mapping; // maps from existing/old temp number to the new and unique temp number. enum { Absent = -1 }; Mapping vregMapping; ProcessedBlocks processed; BasicBlock *currentBB; Stmt *currentStmt; struct TodoAction { enum { RestoreVReg, Rename } action; union { struct { unsigned temp; int previous; } restoreData; struct { BasicBlock *basicBlock; } renameData; }; bool isValid() const { return action != Rename || renameData.basicBlock != 0; } TodoAction() { action = Rename; renameData.basicBlock = 0; } TodoAction(const Temp &t, int prev) { Q_ASSERT(t.kind == Temp::VirtualRegister); action = RestoreVReg; restoreData.temp = t.index; restoreData.previous = prev; } TodoAction(BasicBlock *bb) { Q_ASSERT(bb); action = Rename; renameData.basicBlock = bb; } }; QVector todo; public: VariableRenamer(IR::Function *f, DefUses &defUses) : function(f) , defUses(defUses) , tempCount(0) , processed(f) { vregMapping.assign(f->tempCount, Absent); todo.reserve(f->basicBlockCount()); } void run() { todo.append(TodoAction(function->basicBlock(0))); while (!todo.isEmpty()) { TodoAction todoAction = todo.back(); Q_ASSERT(todoAction.isValid()); todo.pop_back(); switch (todoAction.action) { case TodoAction::Rename: rename(todoAction.renameData.basicBlock); break; case TodoAction::RestoreVReg: restore(vregMapping, todoAction.restoreData.temp, todoAction.restoreData.previous); break; default: Q_UNREACHABLE(); } } function->tempCount = tempCount; } private: static inline void restore(Mapping &mapping, int temp, int previous) { mapping[temp] = previous; } void rename(BasicBlock *bb) { while (bb && !processed.alreadyProcessed(bb)) { renameStatementsAndPhis(bb); processed.markAsProcessed(bb); BasicBlock *next = 0; for (BasicBlock *out : bb->out) { if (processed.alreadyProcessed(out)) continue; if (!next) next = out; else todo.append(TodoAction(out)); } bb = next; } } void renameStatementsAndPhis(BasicBlock *bb) { currentBB = bb; for (Stmt *s : bb->statements()) { currentStmt = s; visit(s); } for (BasicBlock *Y : bb->out) { const int j = Y->in.indexOf(bb); Q_ASSERT(j >= 0 && j < Y->in.size()); for (Stmt *s : Y->statements()) { if (Phi *phi = s->asPhi()) { Temp *t = phi->incoming[j]->asTemp(); unsigned newTmp = currentNumber(*t); // qDebug()<<"I: replacing phi use"<index; t->index = newTmp; t->kind = Temp::VirtualRegister; defUses.addUse(*t, phi); } else { break; } } } } unsigned currentNumber(const Temp &t) { int nr = Absent; switch (t.kind) { case Temp::VirtualRegister: nr = vregMapping[t.index]; break; default: Q_UNREACHABLE(); nr = Absent; break; } if (nr == Absent) { // Special case: we didn't prune the Phi nodes yet, so for proper temps (virtual // registers) the SSA algorithm might insert superfluous Phis that have uses without // definition. E.g.: if a temporary got introduced in the "then" clause, it "could" // reach the "end-if" block, so there will be a phi node for that temp. A later pass // will clean this up by looking for uses-without-defines in phi nodes. So, what we do // is to generate a new unique number, and leave it dangling. nr = nextFreeTemp(t); } return nr; } unsigned nextFreeTemp(const Temp &t) { unsigned newIndex = tempCount++; Q_ASSERT(newIndex <= INT_MAX); int oldIndex = Absent; switch (t.kind) { case Temp::VirtualRegister: oldIndex = vregMapping[t.index]; vregMapping[t.index] = newIndex; break; default: Q_UNREACHABLE(); } todo.append(TodoAction(t, oldIndex)); return newIndex; } private: void visit(Stmt *s) { if (auto move = s->asMove()) { // uses: visit(move->source); // defs: if (Temp *t = move->target->asTemp()) { renameTemp(t); } else { visit(move->target); } } else if (auto phi = s->asPhi()) { renameTemp(phi->targetTemp); } else { STMT_VISIT_ALL_KINDS(s); } } void visit(Expr *e) { if (auto temp = e->asTemp()) { temp->index = currentNumber(*temp); temp->kind = Temp::VirtualRegister; defUses.addUse(*temp, currentStmt); } else { EXPR_VISIT_ALL_KINDS(e); } } void renameTemp(Temp *t) { // only called for defs, not uses const int newIdx = nextFreeTemp(*t); // qDebug()<<"I: replacing def of"<kind = Temp::VirtualRegister; t->index = newIdx; defUses.addDef(t, currentStmt, currentBB); } }; // This function converts the IR to semi-pruned SSA form. For details about SSA and the algorightm, // see [Appel]. For the changes needed for semi-pruned SSA form, and for its advantages, see [Briggs]. void convertToSSA(IR::Function *function, const DominatorTree &df, DefUses &defUses) { // Collect all applicable variables: VariableCollector variables(function); // Prepare for phi node insertion: std::vector A_phi; const size_t ei = function->basicBlockCount(); A_phi.resize(ei); for (size_t i = 0; i != ei; ++i) A_phi[i].assign(function->tempCount, false); std::vector W; W.reserve(8); // Place phi functions: for (const Temp &a : variables.allTemps()) { if (a.isInvalid()) continue; if (!variables.isNonLocal(a)) continue; // for semi-pruned SSA W.clear(); variables.collectDefSites(a, W); while (!W.empty()) { BasicBlock *n = W.back(); W.pop_back(); const BasicBlockSet &dominatorFrontierForN = df.dominatorFrontier(n); for (BasicBlockSet::const_iterator it = dominatorFrontierForN.begin(), eit = dominatorFrontierForN.end(); it != eit; ++it) { BasicBlock *y = *it; if (!A_phi.at(y->index()).at(a.index)) { insertPhiNode(a, y, function); A_phi[y->index()].setBit(a.index); const std::vector &varsInBlockY = variables.inBlock(y); if (std::find(varsInBlockY.begin(), varsInBlockY.end(), a.index) == varsInBlockY.end()) W.push_back(y); } } } } // Rename variables: VariableRenamer(function, defUses).run(); } /// Calculate if a phi node result is used only by other phi nodes, and if those uses are /// in turn also used by other phi nodes. bool hasPhiOnlyUses(Phi *phi, const DefUses &defUses, QBitArray &collectedPhis) { collectedPhis.setBit(phi->id()); for (Stmt *use : defUses.uses(*phi->targetTemp)) { Phi *dependentPhi = use->asPhi(); if (!dependentPhi) return false; // there is a use by a non-phi node if (collectedPhis.at(dependentPhi->id())) continue; // we already found this node if (!hasPhiOnlyUses(dependentPhi, defUses, collectedPhis)) return false; } return true; } void cleanupPhis(DefUses &defUses) { QBitArray toRemove(defUses.statementCount()); QBitArray collectedPhis(defUses.statementCount()); std::vector allPhis; allPhis.reserve(32); for (const Temp *def : defUses.defs()) { Stmt *defStmt = defUses.defStmt(*def); if (!defStmt) continue; Phi *phi = defStmt->asPhi(); if (!phi) continue; allPhis.push_back(phi); if (toRemove.at(phi->id())) continue; collectedPhis.fill(false); if (hasPhiOnlyUses(phi, defUses, collectedPhis)) toRemove |= collectedPhis; } for (Phi *phi : allPhis) { if (!toRemove.at(phi->id())) continue; const Temp &targetVar = *phi->targetTemp; defUses.defStmtBlock(targetVar)->removeStatement(phi); for (const Temp &usedVar : defUses.usedVars(phi)) defUses.removeUse(phi, usedVar); defUses.removeDef(targetVar); } defUses.cleanup(); } class StatementWorklist { IR::Function *theFunction; std::vector stmts; BitVector worklist; unsigned worklistSize; std::vector replaced; BitVector removed; Q_DISABLE_COPY(StatementWorklist) public: StatementWorklist(IR::Function *function) : theFunction(function) , stmts(function->statementCount(), static_cast(0)) , worklist(function->statementCount(), false) , worklistSize(0) , replaced(function->statementCount(), Stmt::InvalidId) , removed(function->statementCount()) { grow(); for (BasicBlock *bb : function->basicBlocks()) { if (bb->isRemoved()) continue; for (Stmt *s : bb->statements()) { if (!s) continue; stmts[s->id()] = s; worklist.setBit(s->id()); ++worklistSize; } } } void reset() { worklist.assign(worklist.size(), false); worklistSize = 0; for (Stmt *s : stmts) { if (!s) continue; worklist.setBit(s->id()); ++worklistSize; } replaced.assign(replaced.size(), Stmt::InvalidId); removed.assign(removed.size(), false); } void remove(Stmt *stmt) { replaced[stmt->id()] = Stmt::InvalidId; removed.setBit(stmt->id()); if (worklist.at(stmt->id())) { worklist.clearBit(stmt->id()); Q_ASSERT(worklistSize > 0); --worklistSize; } } void replace(Stmt *oldStmt, Stmt *newStmt) { Q_ASSERT(oldStmt); Q_ASSERT(replaced[oldStmt->id()] == Stmt::InvalidId); Q_ASSERT(removed.at(oldStmt->id()) == false); Q_ASSERT(newStmt); registerNewStatement(newStmt); Q_ASSERT(replaced[newStmt->id()] == Stmt::InvalidId); Q_ASSERT(removed.at(newStmt->id()) == false); replaced[oldStmt->id()] = newStmt->id(); worklist.clearBit(oldStmt->id()); } void applyToFunction() { for (BasicBlock *bb : theFunction->basicBlocks()) { if (bb->isRemoved()) continue; for (int i = 0; i < bb->statementCount();) { Stmt *stmt = bb->statements().at(i); int id = stmt->id(); Q_ASSERT(id != Stmt::InvalidId); Q_ASSERT(static_cast(stmt->id()) < stmts.size()); for (int replacementId = replaced[id]; replacementId != Stmt::InvalidId; replacementId = replaced[replacementId]) id = replacementId; Q_ASSERT(id != Stmt::InvalidId); Q_ASSERT(static_cast(stmt->id()) < stmts.size()); if (removed.at(id)) { bb->removeStatement(i); } else { if (id != stmt->id()) bb->replaceStatement(i, stmts[id]); ++i; } } } replaced.assign(replaced.size(), Stmt::InvalidId); removed.assign(removed.size(), false); } StatementWorklist &operator+=(const QVector &stmts) { for (Stmt *s : stmts) this->operator+=(s); return *this; } StatementWorklist &operator+=(Stmt *s) { if (!s) return *this; Q_ASSERT(s->id() >= 0); Q_ASSERT(s->id() < worklist.size()); if (!worklist.at(s->id())) { worklist.setBit(s->id()); ++worklistSize; } return *this; } StatementWorklist &operator-=(Stmt *s) { Q_ASSERT(s->id() >= 0); Q_ASSERT(s->id() < worklist.size()); if (worklist.at(s->id())) { worklist.clearBit(s->id()); Q_ASSERT(worklistSize > 0); --worklistSize; } return *this; } unsigned size() const { return worklistSize; } Stmt *takeNext(Stmt *last) { if (worklistSize == 0) return 0; const int startAt = last ? last->id() + 1 : 0; Q_ASSERT(startAt >= 0); Q_ASSERT(startAt <= worklist.size()); Q_ASSERT(static_cast(worklist.size()) == stmts.size()); int pos = worklist.findNext(startAt, true, /*wrapAround = */true); worklist.clearBit(pos); Q_ASSERT(worklistSize > 0); --worklistSize; Stmt *s = stmts.at(pos); Q_ASSERT(s); if (removed.at(s->id())) return takeNext(s); return s; } IR::Function *function() const { return theFunction; } void registerNewStatement(Stmt *s) { Q_ASSERT(s->id() >= 0); if (static_cast(s->id()) >= stmts.size()) { if (static_cast(s->id()) >= stmts.capacity()) grow(); int newSize = s->id() + 1; stmts.resize(newSize, 0); worklist.resize(newSize); replaced.resize(newSize, Stmt::InvalidId); removed.resize(newSize); } stmts[s->id()] = s; } private: void grow() { Q_ASSERT(stmts.capacity() < INT_MAX / 2); int newCapacity = ((static_cast(stmts.capacity()) + 1) * 3) / 2; stmts.reserve(newCapacity); worklist.reserve(newCapacity); replaced.reserve(newCapacity); removed.reserve(newCapacity); } }; class SideEffectsChecker { bool _sideEffect; public: SideEffectsChecker() : _sideEffect(false) {} ~SideEffectsChecker() {} bool hasSideEffects(Expr *expr) { bool sideEffect = false; qSwap(_sideEffect, sideEffect); visit(expr); qSwap(_sideEffect, sideEffect); return sideEffect; } protected: void markAsSideEffect() { _sideEffect = true; } bool seenSideEffects() const { return _sideEffect; } void visit(Expr *e) { if (auto n = e->asName()) { visitName(n); } else if (auto t = e->asTemp()) { visitTemp(t); } else if (auto c = e->asClosure()) { visitClosure(c); } else if (auto c = e->asConvert()) { visitConvert(c); } else if (auto u = e->asUnop()) { visitUnop(u); } else if (auto b = e->asBinop()) { visitBinop(b); } else if (auto c = e->asCall()) { visitCall(c); } else if (auto n = e->asNew()) { visitNew(n); } else if (auto s = e->asSubscript()) { visitSubscript(s); } else if (auto m = e->asMember()) { visitMember(m); } } virtual void visitTemp(Temp *) {} private: void visitName(Name *e) { if (e->freeOfSideEffects) return; // TODO: maybe we can distinguish between built-ins of which we know that they do not have // a side-effect. if (e->builtin == Name::builtin_invalid || (e->id && *e->id != QLatin1String("this"))) markAsSideEffect(); } void visitClosure(Closure *) { markAsSideEffect(); } void visitConvert(Convert *e) { visit(e->expr); switch (e->expr->type) { case QObjectType: case StringType: case VarType: markAsSideEffect(); break; default: break; } } void visitUnop(Unop *e) { visit(e->expr); switch (e->op) { case OpUPlus: case OpUMinus: case OpNot: case OpIncrement: case OpDecrement: if (e->expr->type == VarType || e->expr->type == StringType || e->expr->type == QObjectType) markAsSideEffect(); break; default: break; } } void visitBinop(Binop *e) { // TODO: prune parts that don't have a side-effect. For example, in: // function f(x) { +x+1; return 0; } // we can prune the binop and leave the unop/conversion. _sideEffect = hasSideEffects(e->left); _sideEffect |= hasSideEffects(e->right); if (e->left->type == VarType || e->left->type == StringType || e->left->type == QObjectType || e->right->type == VarType || e->right->type == StringType || e->right->type == QObjectType) markAsSideEffect(); } void visitSubscript(Subscript *e) { visit(e->base); visit(e->index); markAsSideEffect(); } void visitMember(Member *e) { visit(e->base); if (e->freeOfSideEffects) return; markAsSideEffect(); } void visitCall(Call *e) { visit(e->base); for (ExprList *args = e->args; args; args = args->next) visit(args->expr); markAsSideEffect(); // TODO: there are built-in functions that have no side effect. } void visitNew(New *e) { visit(e->base); for (ExprList *args = e->args; args; args = args->next) visit(args->expr); markAsSideEffect(); // TODO: there are built-in types that have no side effect. } }; class EliminateDeadCode: public SideEffectsChecker { DefUses &_defUses; StatementWorklist &_worklist; QVarLengthArray _collectedTemps; public: EliminateDeadCode(DefUses &defUses, StatementWorklist &worklist) : _defUses(defUses) , _worklist(worklist) {} void run(Expr *&expr, Stmt *stmt) { _collectedTemps.clear(); if (!hasSideEffects(expr)) { expr = 0; for (Temp *t : _collectedTemps) { _defUses.removeUse(stmt, *t); _worklist += _defUses.defStmt(*t); } } } protected: void visitTemp(Temp *e) Q_DECL_OVERRIDE Q_DECL_FINAL { _collectedTemps.append(e); } }; class PropagateTempTypes { const DefUses &defUses; UntypedTemp theTemp; DiscoveredType newType; public: PropagateTempTypes(const DefUses &defUses) : defUses(defUses) {} void run(const UntypedTemp &temp, const DiscoveredType &type) { newType = type; theTemp = temp; if (Stmt *defStmt = defUses.defStmt(temp.temp)) visit(defStmt); for (Stmt *use : defUses.uses(temp.temp)) visit(use); } private: void visit(Stmt *s) { STMT_VISIT_ALL_KINDS(s); } void visit(Expr *e) { if (auto temp = e->asTemp()) { if (theTemp == UntypedTemp(*temp)) { temp->type = static_cast(newType.type); temp->memberResolver = newType.memberResolver; } } else { EXPR_VISIT_ALL_KINDS(e); } } }; class TypeInference { enum { DebugTypeInference = 0 }; QQmlEnginePrivate *qmlEngine; const DefUses &_defUses; typedef std::vector TempTypes; TempTypes _tempTypes; StatementWorklist *_worklist; struct TypingResult { DiscoveredType type; bool fullyTyped; TypingResult(const DiscoveredType &type = DiscoveredType()) { #if defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 6 // avoid optimization bug in gcc 4.6.3 armhf ((int volatile &) this->type.type) = type.type; #endif this->type = type; fullyTyped = type.type != UnknownType; } explicit TypingResult(MemberExpressionResolver *memberResolver) : type(memberResolver) , fullyTyped(true) {} }; TypingResult _ty; Stmt *_currentStmt; public: TypeInference(QQmlEnginePrivate *qmlEngine, const DefUses &defUses) : qmlEngine(qmlEngine) , _defUses(defUses) , _tempTypes(_defUses.tempCount()) , _worklist(0) , _ty(UnknownType) , _currentStmt(nullptr) {} void run(StatementWorklist &w) { _worklist = &w; Stmt *s = 0; while ((s = _worklist->takeNext(s))) { if (s->asJump()) continue; if (DebugTypeInference) { QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream qout(&buf); qout<<"Typing stmt "; IRPrinter(&qout).print(s); qout.flush(); qDebug("%s", buf.data().constData()); qDebug("%u left in the worklist", _worklist->size()); } if (!run(s)) { *_worklist += s; if (DebugTypeInference) { QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream qout(&buf); qout<<"Pushing back stmt: "; IRPrinter(&qout).print(s); qout.flush(); qDebug("%s", buf.data().constData()); } } else { if (DebugTypeInference) { QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream qout(&buf); qout<<"Finished: "; IRPrinter(&qout).print(s); qout.flush(); qDebug("%s", buf.data().constData()); } } } PropagateTempTypes propagator(_defUses); for (size_t i = 0, ei = _tempTypes.size(); i != ei; ++i) { const Temp &temp = _defUses.temp(int(i)); if (temp.kind == Temp::Invalid) continue; const DiscoveredType &tempType = _tempTypes[i]; if (tempType.type == UnknownType) continue; propagator.run(temp, tempType); } _worklist = 0; } private: bool run(Stmt *s) { TypingResult ty; std::swap(_ty, ty); std::swap(_currentStmt, s); visit(_currentStmt); std::swap(_currentStmt, s); std::swap(_ty, ty); return ty.fullyTyped; } TypingResult run(Expr *e) { TypingResult ty; std::swap(_ty, ty); visit(e); std::swap(_ty, ty); if (ty.type != UnknownType) setType(e, ty.type); return ty; } void setType(Expr *e, DiscoveredType ty) { if (Temp *t = e->asTemp()) { if (DebugTypeInference) qDebug() << "Setting type for temp" << t->index << " to " << typeName(Type(ty.type)) << "(" << ty.type << ")" << endl; DiscoveredType &it = _tempTypes[t->index]; if (it != ty) { it = ty; if (DebugTypeInference) { for (Stmt *s : _defUses.uses(*t)) { QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream qout(&buf); qout << "Pushing back dependent stmt: "; IRPrinter(&qout).print(s); qout.flush(); qDebug("%s", buf.data().constData()); } } for (Stmt *s : qAsConst(_defUses.uses(*t))) { if (s != _currentStmt) { *_worklist += s; } } } } else { e->type = (Type) ty.type; } } private: void visit(Expr *e) { if (auto c = e->asConst()) { visitConst(c); } else if (auto s = e->asString()) { visitString(s); } else if (auto r = e->asRegExp()) { visitRegExp(r); } else if (auto n = e->asName()) { visitName(n); } else if (auto t = e->asTemp()) { visitTemp(t); } else if (auto a = e->asArgLocal()) { visitArgLocal(a); } else if (auto c = e->asClosure()) { visitClosure(c); } else if (auto c = e->asConvert()) { visitConvert(c); } else if (auto u = e->asUnop()) { visitUnop(u); } else if (auto b = e->asBinop()) { visitBinop(b); } else if (auto c = e->asCall()) { visitCall(c); } else if (auto n = e->asNew()) { visitNew(n); } else if (auto s = e->asSubscript()) { visitSubscript(s); } else if (auto m = e->asMember()) { visitMember(m); } else { Q_UNREACHABLE(); } } void visitConst(Const *c) { if (c->type & NumberType) { if (canConvertToSignedInteger(c->value)) _ty = TypingResult(SInt32Type); else if (canConvertToUnsignedInteger(c->value)) _ty = TypingResult(UInt32Type); else _ty = TypingResult(c->type); } else _ty = TypingResult(c->type); } void visitString(IR::String *) { _ty = TypingResult(StringType); } void visitRegExp(IR::RegExp *) { _ty = TypingResult(VarType); } void visitName(Name *) { _ty = TypingResult(VarType); } void visitTemp(Temp *e) { if (e->memberResolver && e->memberResolver->isValid()) _ty = TypingResult(e->memberResolver); else _ty = TypingResult(_tempTypes[e->index]); setType(e, _ty.type); } void visitArgLocal(ArgLocal *e) { _ty = TypingResult(VarType); setType(e, _ty.type); } void visitClosure(Closure *) { _ty = TypingResult(VarType); } void visitConvert(Convert *e) { _ty = TypingResult(e->type); } void visitUnop(Unop *e) { _ty = run(e->expr); switch (e->op) { case OpUPlus: _ty.type = DoubleType; return; case OpUMinus: _ty.type = DoubleType; return; case OpCompl: _ty.type = SInt32Type; return; case OpNot: _ty.type = BoolType; return; case OpIncrement: case OpDecrement: Q_ASSERT(!"Inplace operators should have been removed!"); Q_UNREACHABLE(); default: Q_UNIMPLEMENTED(); Q_UNREACHABLE(); } } void visitBinop(Binop *e) { TypingResult leftTy = run(e->left); TypingResult rightTy = run(e->right); _ty.fullyTyped = leftTy.fullyTyped && rightTy.fullyTyped; switch (e->op) { case OpAdd: if (leftTy.type.test(VarType) || leftTy.type.test(QObjectType) || rightTy.type.test(VarType) || rightTy.type.test(QObjectType)) _ty.type = VarType; else if (leftTy.type.test(StringType) || rightTy.type.test(StringType)) _ty.type = StringType; else if (leftTy.type != UnknownType && rightTy.type != UnknownType) _ty.type = DoubleType; else _ty.type = UnknownType; break; case OpSub: _ty.type = DoubleType; break; case OpMul: case OpDiv: case OpMod: _ty.type = DoubleType; break; case OpBitAnd: case OpBitOr: case OpBitXor: case OpLShift: case OpRShift: _ty.type = SInt32Type; break; case OpURShift: _ty.type = UInt32Type; break; case OpGt: case OpLt: case OpGe: case OpLe: case OpEqual: case OpNotEqual: case OpStrictEqual: case OpStrictNotEqual: case OpAnd: case OpOr: case OpInstanceof: case OpIn: _ty.type = BoolType; break; default: Q_UNIMPLEMENTED(); Q_UNREACHABLE(); } } void visitCall(Call *e) { _ty = run(e->base); for (ExprList *it = e->args; it; it = it->next) _ty.fullyTyped &= run(it->expr).fullyTyped; _ty.type = VarType; } void visitNew(New *e) { _ty = run(e->base); for (ExprList *it = e->args; it; it = it->next) _ty.fullyTyped &= run(it->expr).fullyTyped; _ty.type = VarType; } void visitSubscript(Subscript *e) { _ty.fullyTyped = run(e->base).fullyTyped && run(e->index).fullyTyped; _ty.type = VarType; } void visitMember(Member *e) { _ty = run(e->base); if (_ty.fullyTyped && _ty.type.memberResolver && _ty.type.memberResolver->isValid()) { MemberExpressionResolver *resolver = _ty.type.memberResolver; _ty.type = resolver->resolveMember(qmlEngine, resolver, e); } else _ty.type = VarType; } void visit(Stmt *s) { if (auto e = s->asExp()) { visitExp(e); } else if (auto m = s->asMove()) { visitMove(m); } else if (auto j = s->asJump()) { visitJump(j); } else if (auto c = s->asCJump()) { visitCJump(c); } else if (auto r = s->asRet()) { visitRet(r); } else if (auto p = s->asPhi()) { visitPhi(p); } else { Q_UNREACHABLE(); } } void visitExp(Exp *s) { _ty = run(s->expr); } void visitMove(Move *s) { if (Temp *t = s->target->asTemp()) { if (Name *n = s->source->asName()) { if (n->builtin == Name::builtin_qml_context) { _ty = TypingResult(t->memberResolver); setType(n, _ty.type); setType(t, _ty.type); return; } } TypingResult sourceTy = run(s->source); setType(t, sourceTy.type); _ty = sourceTy; return; } TypingResult sourceTy = run(s->source); _ty = run(s->target); _ty.fullyTyped &= sourceTy.fullyTyped; } void visitJump(Jump *) { _ty = TypingResult(MissingType); } void visitCJump(CJump *s) { _ty = run(s->cond); } void visitRet(Ret *s) { _ty = run(s->expr); } void visitPhi(Phi *s) { _ty = run(s->incoming[0]); for (int i = 1, ei = s->incoming.size(); i != ei; ++i) { TypingResult ty = run(s->incoming[i]); if (!ty.fullyTyped && _ty.fullyTyped) { // When one of the temps not fully typed, we already know that we cannot completely type this node. // So, pick the type we calculated upto this point, and wait until the unknown one will be typed. // At that point, this statement will be re-scheduled, and then we can fully type this node. _ty.fullyTyped = false; break; } _ty.type.type |= ty.type.type; _ty.fullyTyped &= ty.fullyTyped; if (_ty.type.test(QObjectType) && _ty.type.memberResolver) _ty.type.memberResolver->clear(); // ### TODO: find common ancestor meta-object } switch (_ty.type.type) { case UnknownType: case UndefinedType: case NullType: case BoolType: case SInt32Type: case UInt32Type: case DoubleType: case StringType: case QObjectType: case VarType: // The type is not a combination of two or more types, so we're done. break; default: // There are multiple types involved, so: if (_ty.type.isNumber()) // The type is any combination of double/int32/uint32, but nothing else. So we can // type it as double. _ty.type = DoubleType; else // There just is no single type that can hold this combination, so: _ty.type = VarType; } setType(s->targetTemp, _ty.type); } }; class ReverseInference { const DefUses &_defUses; public: ReverseInference(const DefUses &defUses) : _defUses(defUses) {} void run(IR::Function *f) { Q_UNUSED(f); QVector knownOk; QVector candidates = _defUses.defsUntyped(); while (!candidates.isEmpty()) { UntypedTemp temp = candidates.last(); candidates.removeLast(); if (knownOk.contains(temp)) continue; if (!isUsedAsInt32(temp, knownOk)) continue; Stmt *s = _defUses.defStmt(temp.temp); Move *m = s->asMove(); if (!m) continue; Temp *target = m->target->asTemp(); if (!target || temp != UntypedTemp(*target) || target->type == SInt32Type) continue; if (Temp *t = m->source->asTemp()) { candidates.append(*t); } else if (m->source->asConvert()) { break; } else if (Binop *b = m->source->asBinop()) { bool iterateOnOperands = true; switch (b->op) { case OpSub: case OpMul: case OpAdd: if (b->left->type == SInt32Type && b->right->type == SInt32Type) { iterateOnOperands = false; break; } else { continue; } case OpBitAnd: case OpBitOr: case OpBitXor: case OpLShift: case OpRShift: case OpURShift: break; default: continue; } if (iterateOnOperands) { if (Temp *lt = b->left->asTemp()) candidates.append(*lt); if (Temp *rt = b->right->asTemp()) candidates.append(*rt); } } else if (Unop *u = m->source->asUnop()) { if (u->op == OpCompl || u->op == OpUPlus) { if (Temp *t = u->expr->asTemp()) candidates.append(*t); } } else { continue; } knownOk.append(temp); } PropagateTempTypes propagator(_defUses); for (const UntypedTemp &t : qAsConst(knownOk)) { propagator.run(t, SInt32Type); if (Stmt *defStmt = _defUses.defStmt(t.temp)) { if (Move *m = defStmt->asMove()) { if (Convert *c = m->source->asConvert()) { c->type = SInt32Type; } else if (Unop *u = m->source->asUnop()) { if (u->op != OpUMinus) u->type = SInt32Type; } else if (Binop *b = m->source->asBinop()) { b->type = SInt32Type; } } } } } private: bool isUsedAsInt32(const UntypedTemp &t, const QVector &knownOk) const { const QVector &uses = _defUses.uses(t.temp); if (uses.isEmpty()) return false; for (Stmt *use : uses) { if (Move *m = use->asMove()) { Temp *targetTemp = m->target->asTemp(); if (m->source->asTemp()) { if (!targetTemp || !knownOk.contains(*targetTemp)) return false; } else if (m->source->asConvert()) { continue; } else if (Binop *b = m->source->asBinop()) { switch (b->op) { case OpAdd: case OpSub: case OpMul: if (!targetTemp || !knownOk.contains(*targetTemp)) return false; Q_FALLTHROUGH(); case OpBitAnd: case OpBitOr: case OpBitXor: case OpRShift: case OpLShift: case OpURShift: continue; default: return false; } } else if (Unop *u = m->source->asUnop()) { if (u->op == OpUPlus) { if (!targetTemp || !knownOk.contains(*targetTemp)) return false; } else if (u->op != OpCompl) { return false; } } else { return false; } } else return false; } return true; } }; void convertConst(Const *c, Type targetType) { switch (targetType) { case DoubleType: break; case SInt32Type: c->value = QV4::Primitive::toInt32(c->value); break; case UInt32Type: c->value = QV4::Primitive::toUInt32(c->value); break; case BoolType: c->value = !(c->value == 0 || std::isnan(c->value)); break; case NullType: case UndefinedType: c->value = qt_qnan(); c->type = targetType; break; default: Q_UNIMPLEMENTED(); Q_ASSERT(!"Unimplemented!"); break; } c->type = targetType; } class TypePropagation { DefUses &_defUses; Type _ty; IR::Function *_f; bool run(Expr *&e, Type requestedType = UnknownType, bool insertConversion = true) { qSwap(_ty, requestedType); visit(e); qSwap(_ty, requestedType); if (requestedType != UnknownType) { if (e->type != requestedType) { if (requestedType & NumberType || requestedType == BoolType) { if (insertConversion) addConversion(e, requestedType); return true; } } } return false; } struct Conversion { Expr **expr; Type targetType; Stmt *stmt; Conversion(Expr **expr = 0, Type targetType = UnknownType, Stmt *stmt = 0) : expr(expr) , targetType(targetType) , stmt(stmt) {} }; Stmt *_currStmt; QVector _conversions; void addConversion(Expr *&expr, Type targetType) { _conversions.append(Conversion(&expr, targetType, _currStmt)); } public: TypePropagation(DefUses &defUses) : _defUses(defUses), _ty(UnknownType) {} void run(IR::Function *f, StatementWorklist &worklist) { _f = f; for (BasicBlock *bb : f->basicBlocks()) { if (bb->isRemoved()) continue; _conversions.clear(); for (Stmt *s : bb->statements()) { _currStmt = s; visit(s); } for (const Conversion &conversion : qAsConst(_conversions)) { IR::Move *move = conversion.stmt->asMove(); // Note: isel only supports move into member when source is a temp, so convert // is not a supported source. if (move && move->source->asTemp() && !move->target->asMember()) { *conversion.expr = bb->CONVERT(*conversion.expr, conversion.targetType); } else if (Const *c = (*conversion.expr)->asConst()) { convertConst(c, conversion.targetType); } else if (ArgLocal *al = (*conversion.expr)->asArgLocal()) { Temp *target = bb->TEMP(bb->newTemp(BasicBlock::NewTempForOptimizer)); target->type = conversion.targetType; Expr *convert = bb->CONVERT(al, conversion.targetType); Move *convCall = f->NewStmt(); worklist.registerNewStatement(convCall); convCall->init(target, convert); _defUses.addDef(target, convCall, bb); Temp *source = bb->TEMP(target->index); source->type = conversion.targetType; _defUses.addUse(*source, conversion.stmt); if (conversion.stmt->asPhi()) { // Only temps can be used as arguments to phi nodes, so this is a sanity check...: Q_UNREACHABLE(); } else { bb->insertStatementBefore(conversion.stmt, convCall); } *conversion.expr = source; } else if (Temp *t = (*conversion.expr)->asTemp()) { Temp *target = bb->TEMP(bb->newTemp(BasicBlock::NewTempForOptimizer)); target->type = conversion.targetType; Expr *convert = bb->CONVERT(t, conversion.targetType); Move *convCall = f->NewStmt(); worklist.registerNewStatement(convCall); convCall->init(target, convert); _defUses.addDef(target, convCall, bb); _defUses.addUse(*t, convCall); Temp *source = bb->TEMP(target->index); source->type = conversion.targetType; _defUses.removeUse(conversion.stmt, *t); _defUses.addUse(*source, conversion.stmt); if (Phi *phi = conversion.stmt->asPhi()) { int idx = phi->incoming.indexOf(t); Q_ASSERT(idx != -1); bb->in[idx]->insertStatementBeforeTerminator(convCall); } else { bb->insertStatementBefore(conversion.stmt, convCall); } *conversion.expr = source; } else if (Unop *u = (*conversion.expr)->asUnop()) { // convert: // int32{%2} = double{-double{%1}}; // to: // double{%3} = double{-double{%1}}; // int32{%2} = int32{convert(double{%3})}; Temp *tmp = bb->TEMP(bb->newTemp(BasicBlock::NewTempForOptimizer)); tmp->type = u->type; Move *extraMove = f->NewStmt(); worklist.registerNewStatement(extraMove); extraMove->init(tmp, u); _defUses.addDef(tmp, extraMove, bb); if (Temp *unopOperand = u->expr->asTemp()) { _defUses.addUse(*unopOperand, extraMove); _defUses.removeUse(move, *unopOperand); } bb->insertStatementBefore(conversion.stmt, extraMove); *conversion.expr = bb->CONVERT(CloneExpr::cloneTemp(tmp, f), conversion.targetType); _defUses.addUse(*tmp, move); } else { Q_UNREACHABLE(); } } } } private: void visit(Expr *e) { if (auto c = e->asConst()) { visitConst(c); } else if (auto c = e->asConvert()) { run(c->expr, c->type); } else if (auto u = e->asUnop()) { run(u->expr, u->type); } else if (auto b = e->asBinop()) { visitBinop(b); } else if (auto c = e->asCall()) { visitCall(c); } else if (auto n = e->asNew()) { visitNew(n); } else if (auto s = e->asSubscript()) { visitSubscript(s); } else if (auto m = e->asMember()) { visitMember(m); } } void visitConst(Const *c) { if (_ty & NumberType && c->type & NumberType) { if (_ty == SInt32Type) c->value = QV4::Primitive::toInt32(c->value); else if (_ty == UInt32Type) c->value = QV4::Primitive::toUInt32(c->value); c->type = _ty; } } void visitBinop(Binop *e) { // FIXME: This routine needs more tuning! switch (e->op) { case OpAdd: case OpSub: case OpMul: case OpDiv: case OpMod: case OpBitAnd: case OpBitOr: case OpBitXor: run(e->left, e->type); run(e->right, e->type); break; case OpLShift: case OpRShift: case OpURShift: run(e->left, SInt32Type); run(e->right, SInt32Type); break; case OpGt: case OpLt: case OpGe: case OpLe: case OpEqual: case OpNotEqual: if (e->left->type == DoubleType) { run(e->right, DoubleType); } else if (e->right->type == DoubleType) { run(e->left, DoubleType); } else { run(e->left, e->left->type); run(e->right, e->right->type); } break; case OpStrictEqual: case OpStrictNotEqual: case OpInstanceof: case OpIn: run(e->left, e->left->type); run(e->right, e->right->type); break; default: Q_UNIMPLEMENTED(); Q_UNREACHABLE(); } } void visitCall(Call *e) { run(e->base); for (ExprList *it = e->args; it; it = it->next) run(it->expr); } void visitNew(New *e) { run(e->base); for (ExprList *it = e->args; it; it = it->next) run(it->expr); } void visitSubscript(Subscript *e) { run(e->base); run(e->index); } void visitMember(Member *e) { run(e->base); } void visit(Stmt *s) { if (auto e = s->asExp()) { visitExp(e); } else if (auto m = s->asMove()) { visitMove(m); } else if (auto c = s->asCJump()) { visitCJump(c); } else if (auto r = s->asRet()) { visitRet(r); } else if (auto p = s->asPhi()) { visitPhi(p); } } void visitExp(Exp *s) { run(s->expr); } void visitMove(Move *s) { if (s->source->asConvert()) return; // this statement got inserted for a phi-node type conversion run(s->target); if (Unop *u = s->source->asUnop()) { if (u->op == OpUPlus) { if (run(u->expr, s->target->type, false)) { Convert *convert = _f->New(); convert->init(u->expr, s->target->type); s->source = convert; } else { s->source = u->expr; } return; } } const Member *targetMember = s->target->asMember(); const bool inhibitConversion = targetMember && targetMember->inhibitTypeConversionOnWrite; run(s->source, s->target->type, !inhibitConversion); } void visitCJump(CJump *s) { run(s->cond, BoolType); } void visitRet(Ret *s) { run(s->expr); } void visitPhi(Phi *s) { Type ty = s->targetTemp->type; for (int i = 0, ei = s->incoming.size(); i != ei; ++i) run(s->incoming[i], ty); } }; void splitCriticalEdges(IR::Function *f, DominatorTree &df, StatementWorklist &worklist, DefUses &defUses) { const QVector copy = f->basicBlocks(); for (BasicBlock *toBB : copy) { if (toBB->isRemoved()) continue; if (toBB->in.size() < 2) continue; for (int inIdx = 0, eInIdx = toBB->in.size(); inIdx != eInIdx; ++inIdx) { BasicBlock *fromBB = toBB->in[inIdx]; if (fromBB->out.size() < 2) continue; // We found a critical edge. // create the basic block: BasicBlock *newBB = f->newBasicBlock(toBB->catchBlock); Jump *s = f->NewStmt(); worklist.registerNewStatement(s); defUses.registerNewStatement(s); s->init(toBB); newBB->appendStatement(s); // rewire the old outgoing edge int outIdx = fromBB->out.indexOf(toBB); fromBB->out[outIdx] = newBB; newBB->in.append(fromBB); // rewire the old incoming edge toBB->in[inIdx] = newBB; newBB->out.append(toBB); // add newBB to the correct loop group if (toBB->isGroupStart()) { if (fromBB == toBB) { // special case: the loop header points back to itself (so it's a small loop). newBB->setContainingGroup(toBB); } else { BasicBlock *container; for (container = fromBB->containingGroup(); container; container = container->containingGroup()) if (container == toBB) break; if (container == toBB) // if we were already inside the toBB loop newBB->setContainingGroup(toBB); else newBB->setContainingGroup(toBB->containingGroup()); } } else { newBB->setContainingGroup(toBB->containingGroup()); } // patch the terminator Stmt *terminator = fromBB->terminator(); if (Jump *j = terminator->asJump()) { Q_ASSERT(outIdx == 0); j->target = newBB; } else if (CJump *j = terminator->asCJump()) { if (outIdx == 0) j->iftrue = newBB; else if (outIdx == 1) j->iffalse = newBB; else Q_ASSERT(!"Invalid out edge index for CJUMP!"); } else if (terminator->asRet()) { Q_ASSERT(!"A block with a RET at the end cannot have outgoing edges."); } else { Q_ASSERT(!"Unknown terminator!"); } // qDebug() << "splitting edge" << fromBB->index() << "->" << toBB->index() // << "by inserting block" << newBB->index(); // Set the immediate dominator of the new block to inBB df.setImmediateDominator(newBB, fromBB); bool toNeedsNewIdom = true; for (BasicBlock *bb : toBB->in) { if (bb != newBB && bb != toBB && !df.dominates(toBB, bb)) { toNeedsNewIdom = false; break; } } if (toNeedsNewIdom) df.setImmediateDominator(toBB, newBB); } } } // Detect all (sub-)loops in a function. // // Doing loop detection on the CFG is better than relying on the statement information in // order to mark loops. Although JavaScript only has natural loops, it can still be the case // that something is not a loop even though a loop-like-statement is in the source. For // example: // while (true) { // if (i > 0) // break; // else // break; // } // // Algorithm: // - do a DFS on the dominator tree, where for each node: // - collect all back-edges // - if there are back-edges, the node is a loop-header for a new loop, so: // - walk the CFG is reverse-direction, and for every node: // - if the node already belongs to a loop, we've found a nested loop: // - get the loop-header for the (outermost) nested loop // - add that loop-header to the current loop // - continue by walking all incoming edges that do not yet belong to the current loop // - if the node does not belong to a loop yet, add it to the current loop, and // go on with all incoming edges // // Loop-header detection by checking for back-edges is very straight forward: a back-edge is // an incoming edge where the other node is dominated by the current node. Meaning: all // execution paths that reach that other node have to go through the current node, that other // node ends with a (conditional) jump back to the loop header. // // The exact order of the DFS on the dominator tree is not important. The only property has to // be that a node is only visited when all the nodes it dominates have been visited before. // The reason for the DFS is that for nested loops, the inner loop's loop-header is dominated // by the outer loop's header. So, by visiting depth-first, sub-loops are identified before // their containing loops, which makes nested-loop identification free. An added benefit is // that the nodes for those sub-loops are only processed once. // // Note: independent loops that share the same header are merged together. For example, in // the code snippet below, there are 2 back-edges into the loop-header, but only one single // loop will be detected. // while (a) { // if (b) // continue; // else // continue; // } class LoopDetection { enum { DebugLoopDetection = 0 }; Q_DISABLE_COPY(LoopDetection) public: struct LoopInfo { BasicBlock *loopHeader; QVector loopBody; QVector nestedLoops; LoopInfo *parentLoop; LoopInfo(BasicBlock *loopHeader = 0) : loopHeader(loopHeader) , parentLoop(0) {} bool isValid() const { return loopHeader != 0; } void addNestedLoop(LoopInfo *nested) { Q_ASSERT(nested); Q_ASSERT(!nestedLoops.contains(nested)); Q_ASSERT(nested->parentLoop == 0); nested->parentLoop = this; nestedLoops.append(nested); } }; public: LoopDetection(const DominatorTree &dt) : dt(dt) {} ~LoopDetection() { qDeleteAll(loopInfos); } void run(IR::Function *function) { std::vector backedges; backedges.reserve(4); const auto order = dt.calculateDFNodeIterOrder(); for (BasicBlock *bb : order) { Q_ASSERT(!bb->isRemoved()); backedges.clear(); for (BasicBlock *in : bb->in) if (bb == in || dt.dominates(bb, in)) backedges.push_back(in); if (!backedges.empty()) { subLoop(bb, backedges); } } createLoopInfos(function); dumpLoopInfo(); } void dumpLoopInfo() const { if (!DebugLoopDetection) return; qDebug() << "Found" << loopInfos.size() << "loops"; for (const LoopInfo *info : loopInfos) { qDebug() << "Loop header:" << info->loopHeader->index() << "for loop" << quint64(info); for (BasicBlock *bb : info->loopBody) qDebug() << " " << bb->index(); for (LoopInfo *nested : info->nestedLoops) qDebug() << " sub loop:" << quint64(nested); qDebug() << " parent loop:" << quint64(info->parentLoop); } } QVector allLoops() const { return loopInfos; } // returns all loop headers for loops that have no nested loops. QVector innermostLoops() const { QVector inner(loopInfos); for (int i = 0; i < inner.size(); ) { if (inner.at(i)->nestedLoops.isEmpty()) ++i; else inner.remove(i); } return inner; } private: void subLoop(BasicBlock *loopHead, const std::vector &backedges) { loopHead->markAsGroupStart(); LoopInfo *info = new LoopInfo; info->loopHeader = loopHead; loopInfos.append(info); std::vector worklist; worklist.reserve(backedges.size() + 8); worklist.insert(worklist.end(), backedges.begin(), backedges.end()); while (!worklist.empty()) { BasicBlock *predIt = worklist.back(); worklist.pop_back(); BasicBlock *subloop = predIt->containingGroup(); if (subloop) { // This is a discovered block. Find its outermost discovered loop. while (BasicBlock *parentLoop = subloop->containingGroup()) subloop = parentLoop; // If it is already discovered to be a subloop of this loop, continue. if (subloop == loopHead) continue; // Yay, it's a subloop of this loop. subloop->setContainingGroup(loopHead); predIt = subloop; // Add all predecessors of the subloop header to the worklist, as long as // those predecessors are not in the current subloop. It might be the case // that they are in other loops, which we will then add as a subloop to the // current loop. for (BasicBlock *predIn : predIt->in) if (predIn->containingGroup() != subloop) worklist.push_back(predIn); } else { if (predIt == loopHead) continue; // This is an undiscovered block. Map it to the current loop. predIt->setContainingGroup(loopHead); // Add all incoming edges to the worklist. for (BasicBlock *bb : predIt->in) worklist.push_back(bb); } } } private: const DominatorTree &dt; QVector loopInfos; void createLoopInfos(IR::Function *function) { for (BasicBlock *bb : function->basicBlocks()) { if (bb->isRemoved()) continue; if (BasicBlock *loopHeader = bb->containingGroup()) findLoop(loopHeader)->loopBody.append(bb); } for (int i = 0, size = loopInfos.size(); i < size; ++i) { if (BasicBlock *containingLoopHeader = loopInfos.at(i)->loopHeader->containingGroup()) findLoop(containingLoopHeader)->addNestedLoop(loopInfos.at(i)); } } LoopInfo *findLoop(BasicBlock *loopHeader) { for (LoopInfo *info : qAsConst(loopInfos)) { if (info->loopHeader == loopHeader) return info; } Q_UNREACHABLE(); return nullptr; } }; // High-level algorithm: // 0. start with the first node (the start node) of a function // 1. emit the node // 2. add all outgoing edges that are not yet emitted to the postponed stack // 3. When the postponed stack is empty, pop a stack from the loop stack. If that is empty too, // we're done. // 4. pop a node from the postponed stack, and check if it can be scheduled: // a. if all incoming edges are scheduled, go to 4. // b. if an incoming edge is unscheduled, but it's a back-edge (an edge in a loop that jumps // back to the start of the loop), ignore it // c. if there is any unscheduled edge that is not a back-edge, ignore this node, and go to 4. // 5. if this node is the start of a loop, push the postponed stack on the loop stack. // 6. go back to 1. // // The postponing action in step 2 will put the node into its containing group. The case where this // is important is when a (labeled) continue or a (labeled) break statement occur in a loop: the // outgoing edge points to a node that is not part of the current loop (and possibly not of the // parent loop). // // Linear scan register allocation benefits greatly from short life-time intervals with few holes // (see for example section 4 (Lifetime Analysis) of [Wimmer1]). This algorithm makes sure that the // blocks of a group are scheduled together, with no non-loop blocks in between. This applies // recursively for nested loops. It also schedules groups of if-then-else-endif blocks together for // the same reason. class BlockScheduler { enum { DebugBlockScheduler = 0 }; IR::Function *function; const DominatorTree &dominatorTree; struct WorkForGroup { BasicBlock *group; QStack postponed; WorkForGroup(BasicBlock *group = 0): group(group) {} }; WorkForGroup currentGroup; QStack postponedGroups; QVector sequence; ProcessedBlocks emitted; QHash loopsStartEnd; bool checkCandidate(BasicBlock *candidate) { Q_ASSERT(candidate->containingGroup() == currentGroup.group); for (BasicBlock *in : candidate->in) { if (emitted.alreadyProcessed(in)) continue; if (dominatorTree.dominates(candidate, in)) // this is a loop, where there in -> candidate edge is the jump back to the top of the loop. continue; if (in == candidate) // this is a very tight loop, e.g.: // L1: ... // goto L1 // This can happen when, for example, the basic-block merging gets rid of the empty // body block. In this case, we can safely schedule this block (if all other // incoming edges are either loop-back edges, or have been scheduled already). continue; return false; // an incoming edge that is not yet emitted, and is not a back-edge } if (candidate->isGroupStart()) { // postpone everything, and schedule the loop first. postponedGroups.push(currentGroup); currentGroup = WorkForGroup(candidate); } return true; } BasicBlock *pickNext() { while (true) { while (currentGroup.postponed.isEmpty()) { if (postponedGroups.isEmpty()) return 0; if (currentGroup.group) // record the first and the last node of a group loopsStartEnd.insert(currentGroup.group, sequence.last()); currentGroup = postponedGroups.pop(); } BasicBlock *next = currentGroup.postponed.pop(); if (checkCandidate(next)) return next; } Q_UNREACHABLE(); return 0; } void emitBlock(BasicBlock *bb) { Q_ASSERT(!bb->isRemoved()); if (emitted.alreadyProcessed(bb)) return; sequence.append(bb); emitted.markAsProcessed(bb); } void schedule(BasicBlock *functionEntryPoint) { BasicBlock *next = functionEntryPoint; while (next) { emitBlock(next); for (int i = next->out.size(); i != 0; ) { // postpone all outgoing edges, if they were not already processed --i; BasicBlock *out = next->out[i]; if (!emitted.alreadyProcessed(out)) postpone(out); } next = pickNext(); } } void postpone(BasicBlock *bb) { if (currentGroup.group == bb->containingGroup()) { currentGroup.postponed.append(bb); return; } for (int i = postponedGroups.size(); i != 0; ) { --i; WorkForGroup &g = postponedGroups[i]; if (g.group == bb->containingGroup()) { g.postponed.append(bb); return; } } Q_UNREACHABLE(); } void dumpLoopStartsEnds() const { qDebug() << "Found" << loopsStartEnd.size() << "loops:"; for (auto key : loopsStartEnd.keys()) qDebug("Loop starting at L%d ends at L%d.", key->index(), loopsStartEnd.value(key)->index()); } public: BlockScheduler(IR::Function *function, const DominatorTree &dominatorTree) : function(function) , dominatorTree(dominatorTree) , sequence(0) , emitted(function) {} QHash go() { showMeTheCode(function, "Before block scheduling"); if (DebugBlockScheduler) dominatorTree.dumpImmediateDominators(); schedule(function->basicBlock(0)); Q_ASSERT(function->liveBasicBlocksCount() == sequence.size()); function->setScheduledBlocks(sequence); if (DebugBlockScheduler) dumpLoopStartsEnds(); return loopsStartEnd; } }; #ifndef QT_NO_DEBUG void checkCriticalEdges(const QVector &basicBlocks) { for (BasicBlock *bb : basicBlocks) { if (bb && bb->out.size() > 1) { for (BasicBlock *bb2 : bb->out) { if (bb2 && bb2->in.size() > 1) { qDebug() << "found critical edge between block" << bb->index() << "and block" << bb2->index(); Q_ASSERT(false); } } } } } #endif static void cleanupBasicBlocks(IR::Function *function) { showMeTheCode(function, "Before basic block cleanup"); // Algorithm: this is the iterative version of a depth-first search for all blocks that are // reachable through outgoing edges, starting with the start block and all exception handler // blocks. QBitArray reachableBlocks(function->basicBlockCount()); QVarLengthArray postponed; for (int i = 0, ei = function->basicBlockCount(); i != ei; ++i) { BasicBlock *bb = function->basicBlock(i); if (i == 0 || bb->isExceptionHandler()) postponed.append(bb); } while (!postponed.isEmpty()) { BasicBlock *bb = postponed.back(); postponed.pop_back(); if (bb->isRemoved()) // this block was removed before, we don't need to clean it up. continue; reachableBlocks.setBit(bb->index()); for (BasicBlock *outBB : bb->out) { if (!reachableBlocks.at(outBB->index())) postponed.append(outBB); } } for (BasicBlock *bb : function->basicBlocks()) { if (bb->isRemoved()) // the block has already been removed, so ignore it continue; if (reachableBlocks.at(bb->index())) // the block is reachable, so ignore it continue; for (BasicBlock *outBB : bb->out) { if (outBB->isRemoved() || !reachableBlocks.at(outBB->index())) continue; // We do not need to unlink from blocks that are scheduled to be removed. int idx = outBB->in.indexOf(bb); if (idx != -1) { outBB->in.remove(idx); for (Stmt *s : outBB->statements()) { if (Phi *phi = s->asPhi()) phi->incoming.remove(idx); else break; } } } function->removeBasicBlock(bb); } showMeTheCode(function, "After basic block cleanup"); } inline Const *isConstPhi(Phi *phi) { if (Const *c = phi->incoming[0]->asConst()) { for (int i = 1, ei = phi->incoming.size(); i != ei; ++i) { if (Const *cc = phi->incoming[i]->asConst()) { if (c->value != cc->value) return 0; if (!(c->type == cc->type || (c->type & NumberType && cc->type & NumberType))) return 0; if (int(c->value) == 0 && int(cc->value) == 0) if (isNegative(c->value) != isNegative(cc->value)) return 0; } else { return 0; } } return c; } return 0; } static Expr *clone(Expr *e, IR::Function *function) { if (Temp *t = e->asTemp()) { return CloneExpr::cloneTemp(t, function); } else if (Const *c = e->asConst()) { return CloneExpr::cloneConst(c, function); } else if (Name *n = e->asName()) { return CloneExpr::cloneName(n, function); } else { Q_UNREACHABLE(); return e; } } class ExprReplacer { DefUses &_defUses; IR::Function* _function; Temp *_toReplace; Expr *_replacement; public: ExprReplacer(DefUses &defUses, IR::Function *function) : _defUses(defUses) , _function(function) , _toReplace(0) , _replacement(0) {} bool operator()(Temp *toReplace, Expr *replacement, StatementWorklist &W, QVector *newUses = 0) { Q_ASSERT(replacement->asTemp() || replacement->asConst() || replacement->asName()); qSwap(_toReplace, toReplace); qSwap(_replacement, replacement); const QVector &uses = _defUses.uses(*_toReplace); // Prevent the following: // L3: // %1 = phi L1: %2, L2: %3 // %4 = phi L1: %5, L2: %6 // %6 = %1 // From turning into: // L3: // %1 = phi L1: %2, L2: %3 // %4 = phi L1: %5, L2: %1 // // Because both phi nodes are "executed in parallel", we cannot replace %6 by %1 in the // second phi node. So, if the defining statement for a temp is a phi node, and one of the // uses of the to-be-replaced statement is a phi node in the same block as the defining // statement, bail out. if (Temp *r = _replacement->asTemp()) { if (_defUses.defStmt(*r)->asPhi()) { BasicBlock *replacementDefBlock = _defUses.defStmtBlock(*r); for (Stmt *use : uses) { if (Phi *usePhi = use->asPhi()) { if (_defUses.defStmtBlock(*usePhi->targetTemp) == replacementDefBlock) return false; } } } } // qout << "Replacing ";toReplace->dump(qout);qout<<" by ";replacement->dump(qout);qout<reserve(uses.size()); // qout << " " << uses.size() << " uses:"<dump(qout);qout<<"\n"; visit(use); // qout<<" -> ";use->dump(qout);qout<<"\n"; W += use; if (newUses) newUses->push_back(use); } qSwap(_replacement, replacement); qSwap(_toReplace, toReplace); return true; } private: void visit(Expr *e) { if (auto c = e->asConst()) { visitConst(c); } else if (auto s = e->asString()) { visitString(s); } else if (auto r = e->asRegExp()) { visitRegExp(r); } else if (auto n = e->asName()) { visitName(n); } else if (auto t = e->asTemp()) { visitTemp(t); } else if (auto a = e->asArgLocal()) { visitArgLocal(a); } else if (auto c = e->asClosure()) { visitClosure(c); } else if (auto c = e->asConvert()) { visitConvert(c); } else if (auto u = e->asUnop()) { visitUnop(u); } else if (auto b = e->asBinop()) { visitBinop(b); } else if (auto c = e->asCall()) { visitCall(c); } else if (auto n = e->asNew()) { visitNew(n); } else if (auto s = e->asSubscript()) { visitSubscript(s); } else if (auto m = e->asMember()) { visitMember(m); } else { Q_UNREACHABLE(); } } void visitConst(Const *) {} void visitString(IR::String *) {} void visitRegExp(IR::RegExp *) {} void visitName(Name *) {} void visitTemp(Temp *) {} void visitArgLocal(ArgLocal *) {} void visitClosure(Closure *) {} void visitConvert(Convert *e) { check(e->expr); } void visitUnop(Unop *e) { check(e->expr); } void visitBinop(Binop *e) { check(e->left); check(e->right); } void visitCall(Call *e) { check(e->base); for (ExprList *it = e->args; it; it = it->next) check(it->expr); } void visitNew(New *e) { check(e->base); for (ExprList *it = e->args; it; it = it->next) check(it->expr); } void visitSubscript(Subscript *e) { check(e->base); check(e->index); } void visitMember(Member *e) { check(e->base); } void visit(Stmt *s) { if (auto e = s->asExp()) { visitExp(e); } else if (auto m = s->asMove()) { visitMove(m); } else if (auto j = s->asJump()) { visitJump(j); } else if (auto c = s->asCJump()) { visitCJump(c); } else if (auto r = s->asRet()) { visitRet(r); } else if (auto p = s->asPhi()) { visitPhi(p); } else { Q_UNREACHABLE(); } } void visitExp(Exp *s) { check(s->expr); } void visitMove(Move *s) { check(s->target); check(s->source); } void visitJump(Jump *) {} void visitCJump(CJump *s) { check(s->cond); } void visitRet(Ret *s) { check(s->expr); } void visitPhi(Phi *s) { for (int i = 0, ei = s->incoming.size(); i != ei; ++i) check(s->incoming[i]); } private: void check(Expr *&e) { if (equals(e, _toReplace)) { e = clone(_replacement, _function); } else { visit(e); } } // This only calculates equality for everything needed by constant propagation bool equals(Expr *e1, Expr *e2) const { if (e1 == e2) return true; if (Const *c1 = e1->asConst()) { if (Const *c2 = e2->asConst()) return c1->value == c2->value && (c1->type == c2->type || (c1->type & NumberType && c2->type & NumberType)); } else if (Temp *t1 = e1->asTemp()) { if (Temp *t2 = e2->asTemp()) return *t1 == *t2; } else if (Name *n1 = e1->asName()) { if (Name *n2 = e2->asName()) { if (n1->id) { if (n2->id) return *n1->id == *n2->id; } else { return n1->builtin == n2->builtin; } } } if (e1->type == IR::NullType && e2->type == IR::NullType) return true; if (e1->type == IR::UndefinedType && e2->type == IR::UndefinedType) return true; return false; } }; namespace { /// This function removes the basic-block from the function's list, unlinks any uses and/or defs, /// and removes unreachable staements from the worklist, so that optimiseSSA won't consider them /// anymore. void unlink(BasicBlock *from, BasicBlock *to, IR::Function *func, DefUses &defUses, StatementWorklist &W, DominatorTree &dt) { enum { DebugUnlinking = 0 }; struct Util { static void removeIncomingEdge(BasicBlock *from, BasicBlock *to, DefUses &defUses, StatementWorklist &W) { int idx = to->in.indexOf(from); if (idx == -1) return; to->in.remove(idx); for (Stmt *outStmt : to->statements()) { if (!outStmt) continue; if (Phi *phi = outStmt->asPhi()) { if (Temp *t = phi->incoming[idx]->asTemp()) { defUses.removeUse(phi, *t); W += defUses.defStmt(*t); } phi->incoming.remove(idx); W += phi; } else { break; } } } static bool isReachable(BasicBlock *bb, const DominatorTree &dt) { for (BasicBlock *in : bb->in) { if (in->isRemoved()) continue; if (dt.dominates(bb, in)) // a back-edge, not interesting continue; return true; } return false; } }; Q_ASSERT(!from->isRemoved()); Q_ASSERT(!to->isRemoved()); // don't purge blocks that are entry points for catch statements. They might not be directly // connected, but are required anyway if (to->isExceptionHandler()) return; if (DebugUnlinking) qDebug("Unlinking L%d -> L%d...", from->index(), to->index()); // First, unlink the edge from->out.removeOne(to); Util::removeIncomingEdge(from, to, defUses, W); BasicBlockSet siblings; siblings.init(func); // Check if the target is still reachable... if (Util::isReachable(to, dt)) { // yes, recalculate the immediate dominator, and we're done. if (DebugUnlinking) qDebug(".. L%d is still reachable, recalulate idom.", to->index()); dt.collectSiblings(to, siblings); } else { if (DebugUnlinking) qDebug(".. L%d is unreachable, purging it:", to->index()); // The target is unreachable, so purge it: QVector toPurge; toPurge.reserve(8); toPurge.append(to); while (!toPurge.isEmpty()) { BasicBlock *bb = toPurge.first(); toPurge.removeFirst(); if (DebugUnlinking) qDebug("... purging L%d", bb->index()); if (bb->isRemoved()) continue; // unlink all incoming edges for (BasicBlock *in : bb->in) { int idx = in->out.indexOf(bb); if (idx != -1) in->out.remove(idx); } // unlink all outgoing edges, including "arguments" to phi statements for (BasicBlock *out : bb->out) { if (out->isRemoved()) continue; Util::removeIncomingEdge(bb, out, defUses, W); if (Util::isReachable(out, dt)) { dt.collectSiblings(out, siblings); } else { // if a successor has no incoming edges after unlinking the current basic block, // then it is unreachable, and can be purged too toPurge.append(out); } } // unlink all defs/uses from the statements in the basic block for (Stmt *s : bb->statements()) { if (!s) continue; W += defUses.removeDefUses(s); W -= s; } siblings.remove(bb); dt.setImmediateDominator(bb, 0); func->removeBasicBlock(bb); } } dt.recalculateIDoms(siblings); if (DebugUnlinking) qDebug("Unlinking done."); } bool tryOptimizingComparison(Expr *&expr) { Binop *b = expr->asBinop(); if (!b) return false; Const *leftConst = b->left->asConst(); if (!leftConst || leftConst->type == StringType || leftConst->type == VarType || leftConst->type == QObjectType) return false; Const *rightConst = b->right->asConst(); if (!rightConst || rightConst->type == StringType || rightConst->type == VarType || rightConst->type == QObjectType) return false; QV4::Primitive l = convertToValue(leftConst); QV4::Primitive r = convertToValue(rightConst); switch (b->op) { case OpGt: leftConst->value = Runtime::method_compareGreaterThan(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpLt: leftConst->value = Runtime::method_compareLessThan(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpGe: leftConst->value = Runtime::method_compareGreaterEqual(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpLe: leftConst->value = Runtime::method_compareLessEqual(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpStrictEqual: leftConst->value = Runtime::method_compareStrictEqual(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpEqual: leftConst->value = Runtime::method_compareEqual(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpStrictNotEqual: leftConst->value = Runtime::method_compareStrictNotEqual(l, r); leftConst->type = BoolType; expr = leftConst; return true; case OpNotEqual: leftConst->value = Runtime::method_compareNotEqual(l, r); leftConst->type = BoolType; expr = leftConst; return true; default: break; } return false; } void cfg2dot(IR::Function *f, const QVector &loops = QVector()) { static const bool showCode = qEnvironmentVariableIsSet("QV4_SHOW_IR"); if (!showCode) return; QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream qout(&buf); struct Util { QTextStream &qout; Util(QTextStream &qout): qout(qout) {} void genLoop(const LoopDetection::LoopInfo *loop) { qout << " subgraph \"cluster" << quint64(loop) << "\" {\n"; qout << " L" << loop->loopHeader->index() << ";\n"; for (BasicBlock *bb : loop->loopBody) qout << " L" << bb->index() << ";\n"; for (LoopDetection::LoopInfo *nested : loop->nestedLoops) genLoop(nested); qout << " }\n"; } }; QString name; if (f->name) name = *f->name; else name = QStringLiteral("%1").arg((unsigned long long)f); qout << "digraph \"" << name << "\" { ordering=out;\n"; for (LoopDetection::LoopInfo *l : loops) { if (l->parentLoop == 0) Util(qout).genLoop(l); } for (BasicBlock *bb : f->basicBlocks()) { if (bb->isRemoved()) continue; int idx = bb->index(); qout << " L" << idx << " [label=\"L" << idx << "\""; if (idx == 0 || bb->terminator()->asRet()) qout << ", shape=doublecircle"; else qout << ", shape=circle"; qout << "];\n"; for (BasicBlock *out : bb->out) qout << " L" << idx << " -> L" << out->index() << "\n"; } qout << "}\n"; buf.close(); qDebug("%s", buf.data().constData()); } } // anonymous namespace void optimizeSSA(StatementWorklist &W, DefUses &defUses, DominatorTree &df) { IR::Function *function = W.function(); ExprReplacer replaceUses(defUses, function); Stmt *s = 0; while ((s = W.takeNext(s))) { if (Phi *phi = s->asPhi()) { // dead code elimination: if (defUses.useCount(*phi->targetTemp) == 0) { W += defUses.removeDefUses(phi); W.remove(s); continue; } // constant propagation: if (Const *c = isConstPhi(phi)) { replaceUses(phi->targetTemp, c, W); defUses.removeDef(*phi->targetTemp); W.remove(s); continue; } // copy propagation: if (phi->incoming.size() == 1) { Temp *t = phi->targetTemp; Expr *e = phi->incoming.first(); QVector newT2Uses; replaceUses(t, e, W, &newT2Uses); if (Temp *t2 = e->asTemp()) { defUses.removeUse(s, *t2); defUses.addUses(*t2, newT2Uses); W += defUses.defStmt(*t2); } defUses.removeDef(*t); W.remove(s); continue; } } else if (Move *m = s->asMove()) { if (Convert *convert = m->source->asConvert()) { if (Const *sourceConst = convert->expr->asConst()) { convertConst(sourceConst, convert->type); m->source = sourceConst; W += m; continue; } else if (Temp *sourceTemp = convert->expr->asTemp()) { if (sourceTemp->type == convert->type) { m->source = sourceTemp; W += m; continue; } } } if (Temp *targetTemp = m->target->asTemp()) { // dead code elimination: if (defUses.useCount(*targetTemp) == 0) { EliminateDeadCode(defUses, W).run(m->source, s); if (!m->source) W.remove(s); continue; } // constant propagation: if (Const *sourceConst = m->source->asConst()) { Q_ASSERT(sourceConst->type != UnknownType); replaceUses(targetTemp, sourceConst, W); defUses.removeDef(*targetTemp); W.remove(s); continue; } if (Member *member = m->source->asMember()) { if (member->kind == Member::MemberOfEnum) { Const *c = function->New(); const int enumValue = member->enumValue; c->init(SInt32Type, enumValue); replaceUses(targetTemp, c, W); defUses.removeDef(*targetTemp); W.remove(s); defUses.removeUse(s, *member->base->asTemp()); continue; } else if (member->kind != IR::Member::MemberOfIdObjectsArray && member->attachedPropertiesId != 0 && member->property && member->base->asTemp()) { // Attached properties have no dependency on their base. Isel doesn't // need it and we can eliminate the temp used to initialize it. defUses.removeUse(s, *member->base->asTemp()); Const *c = function->New(); c->init(SInt32Type, 0); member->base = c; continue; } } // copy propagation: if (Temp *sourceTemp = m->source->asTemp()) { QVector newT2Uses; if (replaceUses(targetTemp, sourceTemp, W, &newT2Uses)) { defUses.removeUse(s, *sourceTemp); defUses.addUses(*sourceTemp, newT2Uses); defUses.removeDef(*targetTemp); W.remove(s); } continue; } if (Unop *unop = m->source->asUnop()) { // Constant unary expression evaluation: if (Const *constOperand = unop->expr->asConst()) { if (constOperand->type & NumberType || constOperand->type == BoolType) { // TODO: implement unop propagation for other constant types bool doneSomething = false; switch (unop->op) { case OpNot: constOperand->value = !constOperand->value; constOperand->type = BoolType; doneSomething = true; break; case OpUMinus: if (int(constOperand->value) == 0 && int(constOperand->value) == constOperand->value) { if (isNegative(constOperand->value)) constOperand->value = 0; else constOperand->value = -1 / Q_INFINITY; constOperand->type = DoubleType; doneSomething = true; break; } constOperand->value = -constOperand->value; doneSomething = true; break; case OpUPlus: if (unop->type != UnknownType) constOperand->type = unop->type; doneSomething = true; break; case OpCompl: constOperand->value = ~QV4::Primitive::toInt32(constOperand->value); constOperand->type = SInt32Type; doneSomething = true; break; case OpIncrement: constOperand->value = constOperand->value + 1; doneSomething = true; break; case OpDecrement: constOperand->value = constOperand->value - 1; doneSomething = true; break; default: break; }; if (doneSomething) { m->source = constOperand; W += m; } } } // TODO: if the result of a unary not operation is only used in a cjump, // then inline it. continue; } if (Binop *binop = m->source->asBinop()) { Const *leftConst = binop->left->asConst(); Const *rightConst = binop->right->asConst(); { // Typical casts to int32: Expr *casted = 0; switch (binop->op) { case OpBitAnd: if (leftConst && !rightConst && QV4::Primitive::toUInt32(leftConst->value) == 0xffffffff) casted = binop->right; else if (!leftConst && rightConst && QV4::Primitive::toUInt32(rightConst->value) == 0xffffffff) casted = binop->left; break; case OpBitOr: if (leftConst && !rightConst && QV4::Primitive::toInt32(leftConst->value) == 0) casted = binop->right; else if (!leftConst && rightConst && QV4::Primitive::toUInt32(rightConst->value) == 0) casted = binop->left; break; default: break; } if (casted && casted->type == SInt32Type) { m->source = casted; W += m; continue; } } if (rightConst) { switch (binop->op) { case OpLShift: case OpRShift: if (double v = QV4::Primitive::toInt32(rightConst->value) & 0x1f) { // mask right hand side of shift operations rightConst->value = v; rightConst->type = SInt32Type; } else { // shifting a value over 0 bits is a move: if (rightConst->value == 0) { m->source = binop->left; W += m; } } break; default: break; } } // TODO: More constant binary expression evaluation // TODO: If the result of the move is only used in one single cjump, then // inline the binop into the cjump. if (!leftConst || leftConst->type == StringType || leftConst->type == VarType || leftConst->type == QObjectType) continue; if (!rightConst || rightConst->type == StringType || rightConst->type == VarType || rightConst->type == QObjectType) continue; QV4::Primitive lc = convertToValue(leftConst); QV4::Primitive rc = convertToValue(rightConst); double l = lc.toNumber(); double r = rc.toNumber(); switch (binop->op) { case OpMul: leftConst->value = l * r; leftConst->type = DoubleType; m->source = leftConst; W += m; break; case OpAdd: leftConst->value = l + r; leftConst->type = DoubleType; m->source = leftConst; W += m; break; case OpSub: leftConst->value = l - r; leftConst->type = DoubleType; m->source = leftConst; W += m; break; case OpDiv: leftConst->value = l / r; leftConst->type = DoubleType; m->source = leftConst; W += m; break; case OpMod: leftConst->value = std::fmod(l, r); leftConst->type = DoubleType; m->source = leftConst; W += m; break; default: if (tryOptimizingComparison(m->source)) W += m; break; } continue; } } // TODO: var{#0} = double{%10} where %10 is defined once and used once. E.g.: function(t){t = t % 2; return t; } } else if (CJump *cjump = s->asCJump()) { if (Const *constantCondition = cjump->cond->asConst()) { // Note: this assumes that there are no critical edges! Meaning, we can safely purge // any basic blocks that are found to be unreachable. Jump *jump = function->NewStmt(); W.registerNewStatement(jump); if (convertToValue(constantCondition).toBoolean()) { jump->target = cjump->iftrue; unlink(cjump->parent, cjump->iffalse, function, defUses, W, df); } else { jump->target = cjump->iffalse; unlink(cjump->parent, cjump->iftrue, function, defUses, W, df); } W.replace(s, jump); continue; } else if (cjump->cond->asBinop()) { if (tryOptimizingComparison(cjump->cond)) W += cjump; continue; } // TODO: Constant unary expression evaluation // TODO: if the expression is an unary not operation, lift the expression, and switch // the then/else blocks. } } W.applyToFunction(); } //### TODO: use DefUses from the optimizer, because it already has all this information class InputOutputCollector { void setOutput(Temp *out) { Q_ASSERT(!output); output = out; } public: std::vector inputs; Temp *output; InputOutputCollector() { inputs.reserve(4); } void collect(Stmt *s) { inputs.resize(0); output = 0; visit(s); } private: void visit(Expr *e) { if (auto t = e->asTemp()) { inputs.push_back(t); } else { EXPR_VISIT_ALL_KINDS(e); } } void visit(Stmt *s) { if (auto m = s->asMove()) { visit(m->source); if (Temp *t = m->target->asTemp()) { setOutput(t); } else { visit(m->target); } } else if (s->asPhi()) { // Handled separately } else { STMT_VISIT_ALL_KINDS(s); } } }; /* * The algorithm is described in: * * Linear Scan Register Allocation on SSA Form * Christian Wimmer & Michael Franz, CGO'10, April 24-28, 2010 * * See LifeTimeIntervals::renumber for details on the numbering. */ class LifeRanges { class LiveRegs { typedef std::vector Storage; Storage regs; public: void insert(int r) { if (find(r) == end()) regs.push_back(r); } void unite(const LiveRegs &other) { if (other.empty()) return; if (empty()) { regs = other.regs; return; } for (int r : other.regs) insert(r); } typedef Storage::iterator iterator; iterator find(int r) { return std::find(regs.begin(), regs.end(), r); } iterator begin() { return regs.begin(); } iterator end() { return regs.end(); } void erase(iterator it) { regs.erase(it); } void remove(int r) { iterator it = find(r); if (it != end()) erase(it); } bool empty() const { return regs.empty(); } int size() const { return int(regs.size()); } int at(int idx) const { return regs.at(idx); } }; std::vector _liveIn; std::vector _intervals; LifeTimeIntervals::Ptr _sortedIntervals; LifeTimeInterval &interval(const Temp *temp) { LifeTimeInterval *lti = _intervals[temp->index]; Q_ASSERT(lti); return *lti; } void ensureInterval(const IR::Temp &temp) { Q_ASSERT(!temp.isInvalid()); LifeTimeInterval *<i = _intervals[temp.index]; if (lti) return; lti = new LifeTimeInterval; lti->setTemp(temp); } int defPosition(IR::Stmt *s) const { return usePosition(s) + 1; } int usePosition(IR::Stmt *s) const { return _sortedIntervals->positionForStatement(s); } int start(IR::BasicBlock *bb) const { return _sortedIntervals->startPosition(bb); } int end(IR::BasicBlock *bb) const { return _sortedIntervals->endPosition(bb); } public: LifeRanges(IR::Function *function, const QHash &startEndLoops) : _intervals(function->tempCount) { _sortedIntervals = LifeTimeIntervals::create(function); _liveIn.resize(function->basicBlockCount()); for (int i = function->basicBlockCount() - 1; i >= 0; --i) { BasicBlock *bb = function->basicBlock(i); buildIntervals(bb, startEndLoops.value(bb, 0)); } _intervals.clear(); } LifeTimeIntervals::Ptr intervals() const { return _sortedIntervals; } void dump() const { QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream qout(&buf); qout << "Life ranges:" << endl; qout << "Intervals:" << endl; const auto intervals = _sortedIntervals->intervals(); for (const LifeTimeInterval *range : intervals) { range->dump(qout); qout << endl; } IRPrinter printer(&qout); for (size_t i = 0, ei = _liveIn.size(); i != ei; ++i) { qout << "L" << i <<" live-in: "; auto live = _liveIn.at(i); if (live.empty()) qout << "(none)"; std::sort(live.begin(), live.end()); for (int i = 0; i < live.size(); ++i) { if (i > 0) qout << ", "; qout << '%' << live.at(i); } qout << endl; } buf.close(); qDebug("%s", buf.data().constData()); } private: void buildIntervals(BasicBlock *bb, BasicBlock *loopEnd) { LiveRegs live; for (BasicBlock *successor : bb->out) { live.unite(_liveIn[successor->index()]); const int bbIndex = successor->in.indexOf(bb); Q_ASSERT(bbIndex >= 0); for (Stmt *s : successor->statements()) { if (Phi *phi = s->asPhi()) { if (Temp *t = phi->incoming.at(bbIndex)->asTemp()) { ensureInterval(*t); live.insert(t->index); } } else { break; } } } const QVector &statements = bb->statements(); for (int reg : live) _intervals[reg]->addRange(start(bb), end(bb)); InputOutputCollector collector; for (int i = statements.size() - 1; i >= 0; --i) { Stmt *s = statements.at(i); if (Phi *phi = s->asPhi()) { ensureInterval(*phi->targetTemp); LiveRegs::iterator it = live.find(phi->targetTemp->index); if (it == live.end()) { // a phi node target that is only defined, but never used interval(phi->targetTemp).setFrom(start(bb)); } else { live.erase(it); } _sortedIntervals->add(&interval(phi->targetTemp)); continue; } collector.collect(s); //### TODO: use DefUses from the optimizer, because it already has all this information if (Temp *opd = collector.output) { ensureInterval(*opd); LifeTimeInterval <i = interval(opd); lti.setFrom(defPosition(s)); live.remove(lti.temp().index); _sortedIntervals->add(<i); } //### TODO: use DefUses from the optimizer, because it already has all this information for (size_t i = 0, ei = collector.inputs.size(); i != ei; ++i) { Temp *opd = collector.inputs[i]; ensureInterval(*opd); interval(opd).addRange(start(bb), usePosition(s)); live.insert(opd->index); } } if (loopEnd) { // Meaning: bb is a loop header, because loopEnd is set to non-null. for (int reg : live) _intervals[reg]->addRange(start(bb), usePosition(loopEnd->terminator())); } _liveIn[bb->index()] = std::move(live); } }; void removeUnreachleBlocks(IR::Function *function) { QVector newSchedule; newSchedule.reserve(function->basicBlockCount()); for (BasicBlock *bb : function->basicBlocks()) if (!bb->isRemoved()) newSchedule.append(bb); function->setScheduledBlocks(newSchedule); } class ConvertArgLocals { public: ConvertArgLocals(IR::Function *function) : function(function) , convertArgs(!function->usesArgumentsObject) { tempForFormal.resize(function->formals.size(), -1); tempForLocal.resize(function->locals.size(), -1); } void toTemps() { if (function->variablesCanEscape()) return; QVector extraMoves; if (convertArgs) { const int formalCount = function->formals.size(); extraMoves.reserve(formalCount + function->basicBlock(0)->statementCount()); extraMoves.resize(formalCount); for (int i = 0; i != formalCount; ++i) { const int newTemp = function->tempCount++; tempForFormal[i] = newTemp; ArgLocal *source = function->New(); source->init(ArgLocal::Formal, i, 0); Temp *target = function->New(); target->init(Temp::VirtualRegister, newTemp); Move *m = function->NewStmt(); m->init(target, source); extraMoves[i] = m; } } for (BasicBlock *bb : function->basicBlocks()) { if (!bb->isRemoved()) { for (Stmt *s : bb->statements()) { visit(s); } } } if (convertArgs && function->formals.size() > 0) function->basicBlock(0)->prependStatements(extraMoves); function->locals.clear(); } private: void visit(Stmt *s) { if (auto e = s->asExp()) { check(e->expr); } else if (auto m = s->asMove()) { check(m->target); check(m->source); } else if (auto c = s->asCJump()) { check(c->cond); } else if (auto r = s->asRet()) { check(r->expr); } } void visit(Expr *e) { if (auto c = e->asConvert()) { check(c->expr); } else if (auto u = e->asUnop()) { check(u->expr); } else if (auto b = e->asBinop()) { check(b->left); check(b->right); } else if (auto c = e->asCall()) { check(c->base); for (ExprList *it = c->args; it; it = it->next) { check(it->expr); } } else if (auto n = e->asNew()) { check(n->base); for (ExprList *it = n->args; it; it = it->next) { check(it->expr); } } else if (auto s = e->asSubscript()) { check(s->base); check(s->index); } else if (auto m = e->asMember()) { check(m->base); } } void check(Expr *&e) { if (ArgLocal *al = e->asArgLocal()) { if (al->kind == ArgLocal::Local) { Temp *t = function->New(); t->init(Temp::VirtualRegister, fetchTempForLocal(al->index)); e = t; } else if (convertArgs && al->kind == ArgLocal::Formal) { Temp *t = function->New(); t->init(Temp::VirtualRegister, fetchTempForFormal(al->index)); e = t; } } else { visit(e); } } int fetchTempForLocal(int local) { int &ref = tempForLocal[local]; if (ref == -1) ref = function->tempCount++; return ref; } int fetchTempForFormal(int formal) { return tempForFormal[formal]; } IR::Function *function; bool convertArgs; std::vector tempForFormal; std::vector tempForLocal; }; class CloneBasicBlock: protected CloneExpr { public: BasicBlock *operator()(IR::BasicBlock *originalBlock) { block = new BasicBlock(originalBlock->function, 0); for (Stmt *s : originalBlock->statements()) { visit(s); clonedStmt->location = s->location; } return block; } private: void visit(Stmt *s) { if (auto e = s->asExp()) { clonedStmt = block->EXP(clone(e->expr)); } else if (auto m = s->asMove()) { clonedStmt = block->MOVE(clone(m->target), clone(m->source)); } else if (auto j = s->asJump()) { clonedStmt = block->JUMP(j->target); } else if (auto c = s->asCJump()) { clonedStmt = block->CJUMP(clone(c->cond), c->iftrue, c->iffalse); } else if (auto r = s->asRet()) { clonedStmt = block->RET(clone(r->expr)); } else if (auto p = s->asPhi()) { Phi *phi = block->function->NewStmt(); clonedStmt = phi; phi->targetTemp = clone(p->targetTemp); for (Expr *in : p->incoming) phi->incoming.append(clone(in)); block->appendStatement(phi); } else { Q_UNREACHABLE(); } } private: IR::Stmt *clonedStmt; }; static void verifyCFG(IR::Function *function) { if (!DoVerification) return; for (BasicBlock *bb : function->basicBlocks()) { if (bb->isRemoved()) { Q_ASSERT(bb->in.isEmpty()); Q_ASSERT(bb->out.isEmpty()); continue; } Q_ASSERT(function->basicBlock(bb->index()) == bb); // Check the terminators: Stmt *terminator = bb->terminator(); if (terminator == nullptr) { Stmt *last = bb->statements().last(); Call *call = last->asExp()->expr->asCall(); Name *baseName = call->base->asName(); Q_ASSERT(baseName->builtin == Name::builtin_rethrow); Q_UNUSED(baseName); } else if (Jump *jump = terminator->asJump()) { Q_UNUSED(jump); Q_ASSERT(jump->target); Q_ASSERT(!jump->target->isRemoved()); Q_ASSERT(bb->out.size() == 1); Q_ASSERT(bb->out.first() == jump->target); } else if (CJump *cjump = terminator->asCJump()) { Q_UNUSED(cjump); Q_ASSERT(bb->out.size() == 2); Q_ASSERT(cjump->iftrue); Q_ASSERT(!cjump->iftrue->isRemoved()); Q_ASSERT(cjump->iftrue == bb->out[0]); Q_ASSERT(cjump->iffalse); Q_ASSERT(!cjump->iffalse->isRemoved()); Q_ASSERT(cjump->iffalse == bb->out[1]); } else if (terminator->asRet()) { Q_ASSERT(bb->out.size() == 0); } else { Q_UNREACHABLE(); } // Check the outgoing edges: for (BasicBlock *out : bb->out) { Q_UNUSED(out); Q_ASSERT(!out->isRemoved()); Q_ASSERT(out->in.contains(bb)); } // Check the incoming edges: for (BasicBlock *in : bb->in) { Q_UNUSED(in); Q_ASSERT(!in->isRemoved()); Q_ASSERT(in->out.contains(bb)); } } } static void verifyImmediateDominators(const DominatorTree &dt, IR::Function *function) { if (!DoVerification) return; cfg2dot(function); dt.dumpImmediateDominators(); DominatorTree referenceTree(function); for (BasicBlock *bb : function->basicBlocks()) { if (bb->isRemoved()) continue; BasicBlock *idom = dt.immediateDominator(bb); BasicBlock *referenceIdom = referenceTree.immediateDominator(bb); Q_UNUSED(idom); Q_UNUSED(referenceIdom); Q_ASSERT(idom == referenceIdom); } } static void verifyNoPointerSharing(IR::Function *function) { if (!DoVerification) return; class { public: void operator()(IR::Function *f) { for (BasicBlock *bb : f->basicBlocks()) { if (bb->isRemoved()) continue; for (Stmt *s : bb->statements()) { visit(s); } } } private: void visit(Stmt *s) { check(s); STMT_VISIT_ALL_KINDS(s); } void visit(Expr *e) { check(e); EXPR_VISIT_ALL_KINDS(e); } private: void check(Stmt *s) { Q_ASSERT(!stmts.contains(s)); stmts.insert(s); } void check(Expr *e) { Q_ASSERT(!exprs.contains(e)); exprs.insert(e); } QSet stmts; QSet exprs; } V; V(function); } // Loop-peeling is done by unfolding the loop once. The "original" loop basic blocks stay where they // are, and a copy of the loop is placed after it. Special care is taken while copying the loop body: // by having the copies of the basic-blocks point to the same nodes as the "original" basic blocks, // updating the immediate dominators is easy: if the edge of a copied basic-block B points to a // block C that has also been copied, set the immediate dominator of B to the corresponding // immediate dominator of C. Finally, for any node outside the loop that gets a new edge attached, // the immediate dominator has to be re-calculated. class LoopPeeling { DominatorTree &dt; public: LoopPeeling(DominatorTree &dt) : dt(dt) {} void run(const QVector &loops) { for (LoopDetection::LoopInfo *loopInfo : loops) peelLoop(loopInfo); } private: // All copies have their outgoing edges pointing to the same successor block as the originals. // For each copied block, check where the outgoing edges point to. If it's a block inside the // (original) loop, rewire it to the corresponding copy. Otherwise, which is when it points // out of the loop, leave it alone. // As an extra, collect all edges that point out of the copied loop, because the targets need // to have their immediate dominator rechecked. void rewire(BasicBlock *newLoopBlock, const QVector &from, const QVector &to, QVector &loopExits) { for (int i = 0, ei = newLoopBlock->out.size(); i != ei; ++i) { BasicBlock *&out = newLoopBlock->out[i]; const int idx = from.indexOf(out); if (idx == -1) { if (!loopExits.contains(out)) loopExits.append(out); } else { out->in.removeOne(newLoopBlock); BasicBlock *newTo = to.at(idx); newTo->in.append(newLoopBlock); out = newTo; Stmt *terminator = newLoopBlock->terminator(); if (Jump *jump = terminator->asJump()) { Q_ASSERT(i == 0); jump->target = newTo; } else if (CJump *cjump = terminator->asCJump()) { Q_ASSERT(i == 0 || i == 1); if (i == 0) cjump->iftrue = newTo; else cjump->iffalse = newTo; } } } } void peelLoop(LoopDetection::LoopInfo *loop) { IR::Function *f = loop->loopHeader->function; CloneBasicBlock clone; LoopDetection::LoopInfo unpeeled(*loop); unpeeled.loopHeader = clone(unpeeled.loopHeader); unpeeled.loopHeader->setContainingGroup(loop->loopHeader->containingGroup()); unpeeled.loopHeader->markAsGroupStart(true); f->addBasicBlock(unpeeled.loopHeader); for (int i = 0, ei = unpeeled.loopBody.size(); i != ei; ++i) { BasicBlock *&bodyBlock = unpeeled.loopBody[i]; bodyBlock = clone(bodyBlock); bodyBlock->setContainingGroup(unpeeled.loopHeader); Q_ASSERT(bodyBlock->statementCount() == loop->loopBody[i]->statementCount()); } // The cloned blocks will have no incoming edges, but they do have outgoing ones (copying // the terminators will automatically insert that edge). The blocks where the originals // pointed to will have an extra incoming edge from the copied blocks. BasicBlock::IncomingEdges inCopy = loop->loopHeader->in; for (BasicBlock *in : inCopy) { if (loop->loopHeader != in // this can happen for really tight loops (where there are no body blocks). This is a back-edge in that case. && unpeeled.loopHeader != in && !unpeeled.loopBody.contains(in) // if the edge is not coming from within the copied set, leave it alone && !dt.dominates(loop->loopHeader, in)) // an edge coming from within the loop (so a back-edge): this is handled when rewiring all outgoing edges continue; unpeeled.loopHeader->in.append(in); loop->loopHeader->in.removeOne(in); Stmt *terminator = in->terminator(); if (Jump *jump = terminator->asJump()) { jump->target = unpeeled.loopHeader; in->out[0] = unpeeled.loopHeader; } else if (CJump *cjump = terminator->asCJump()) { if (cjump->iftrue == loop->loopHeader) { cjump->iftrue = unpeeled.loopHeader; Q_ASSERT(in->out[0] == loop->loopHeader); in->out[0] = unpeeled.loopHeader; } else if (cjump->iffalse == loop->loopHeader) { cjump->iffalse = unpeeled.loopHeader; Q_ASSERT(in->out[1] == loop->loopHeader); in->out[1] = unpeeled.loopHeader; } else { Q_UNREACHABLE(); } } } QVector loopExits; loopExits.reserve(8); loopExits.append(unpeeled.loopHeader); rewire(unpeeled.loopHeader, loop->loopBody, unpeeled.loopBody, loopExits); for (int i = 0, ei = unpeeled.loopBody.size(); i != ei; ++i) { BasicBlock *bodyBlock = unpeeled.loopBody.at(i); rewire(bodyBlock, loop->loopBody, unpeeled.loopBody, loopExits); f->addBasicBlock(bodyBlock); } // The original loop is now peeled off, and won't jump back to the loop header. Meaning, it // is not a loop anymore, so unmark it. loop->loopHeader->markAsGroupStart(false); for (BasicBlock *bb : qAsConst(loop->loopBody)) bb->setContainingGroup(loop->loopHeader->containingGroup()); // Set the immediate dominator of the new loop header to the old one. The real immediate // dominator will be calculated later. dt.setImmediateDominator(unpeeled.loopHeader, loop->loopHeader); // calculate the idoms in a separate loop, because addBasicBlock in the previous loop will // set the block index, which in turn is used by the dominator tree. for (int i = 0, ei = unpeeled.loopBody.size(); i != ei; ++i) { BasicBlock *bodyBlock = unpeeled.loopBody.at(i); BasicBlock *idom = dt.immediateDominator(loop->loopBody.at(i)); const int idx = loop->loopBody.indexOf(idom); if (idom == loop->loopHeader) idom = unpeeled.loopHeader; else if (idx != -1) idom = unpeeled.loopBody.at(idx); Q_ASSERT(idom); dt.setImmediateDominator(bodyBlock, idom); } BasicBlockSet siblings(f); for (BasicBlock *bb : qAsConst(loopExits)) dt.collectSiblings(bb, siblings); siblings.insert(unpeeled.loopHeader); dt.recalculateIDoms(siblings, loop->loopHeader); dt.dumpImmediateDominators(); verifyImmediateDominators(dt, f); } }; class RemoveLineNumbers: private SideEffectsChecker { public: static void run(IR::Function *function) { for (BasicBlock *bb : function->basicBlocks()) { if (bb->isRemoved()) continue; for (Stmt *s : bb->statements()) { if (!hasSideEffects(s)) { s->location = QQmlJS::AST::SourceLocation(); } } } } private: ~RemoveLineNumbers() {} static bool hasSideEffects(Stmt *stmt) { RemoveLineNumbers checker; if (auto e = stmt->asExp()) { checker.visit(e->expr); } else if (auto m = stmt->asMove()) { checker.visit(m->source); if (!checker.seenSideEffects()) { checker.visit(m->target); } } else if (auto c = stmt->asCJump()) { checker.visit(c->cond); } else if (auto r = stmt->asRet()) { checker.visit(r->expr); } return checker.seenSideEffects(); } void visitTemp(Temp *) Q_DECL_OVERRIDE Q_DECL_FINAL {} }; void mergeBasicBlocks(IR::Function *function, DefUses *du, DominatorTree *dt) { enum { DebugBlockMerging = 0 }; if (function->hasTry) return; showMeTheCode(function, "Before basic block merging"); // Now merge a basic block with its successor when there is one outgoing edge, and the // successor has one incoming edge. for (int i = 0, ei = function->basicBlockCount(); i != ei; ++i) { BasicBlock *bb = function->basicBlock(i); bb->nextLocation = QQmlJS::AST::SourceLocation(); // make sure appendStatement doesn't mess with the line info if (bb->isRemoved()) continue; // the block has been removed, so ignore it if (bb->out.size() != 1) continue; // more than one outgoing edge BasicBlock *successor = bb->out.first(); if (successor->in.size() != 1) continue; // more than one incoming edge // Loop header? No efficient way to update the other blocks that refer to this as containing group, // so don't do merging yet. if (successor->isGroupStart()) continue; // Ok, we can merge the two basic blocks. if (DebugBlockMerging) { qDebug("Merging L%d into L%d", successor->index(), bb->index()); } Q_ASSERT(bb->terminator()->asJump()); bb->removeStatement(bb->statementCount() - 1); // remove the terminator, and replace it with: for (Stmt *s : successor->statements()) { bb->appendStatement(s); // add all statements from the successor to the current basic block if (auto cjump = s->asCJump()) cjump->parent = bb; } bb->out = successor->out; // set the outgoing edges to the successor's so they're now in sync with our new terminator for (auto newSuccessor : bb->out) { for (auto &backlink : newSuccessor->in) { if (backlink == successor) { backlink = bb; // for all successors of our successor: set the incoming edges to come from bb, because we'll now jump there. } } } if (du) { // all statements in successor have moved to bb, so make sure that the containing blocks // stored in DefUses get updated (meaning: point to bb) du->replaceBasicBlock(successor, bb); } if (dt) { // update the immediate dominators to: any block that was dominated by the successor // will now need to point to bb's immediate dominator. The reason is that bb itself // won't be anyones immediate dominator, because it had just one outgoing edge. dt->mergeIntoPredecessor(successor); } function->removeBasicBlock(successor); --i; // re-run on the current basic-block, so any chain gets collapsed. } showMeTheCode(function, "After basic block merging"); verifyCFG(function); } } // anonymous namespace void LifeTimeInterval::setFrom(int from) { Q_ASSERT(from > 0); if (_ranges.isEmpty()) { // this is the case where there is no use, only a define _ranges.prepend(LifeTimeIntervalRange(from, from)); if (_end == InvalidPosition) _end = from; } else { _ranges.first().start = from; } } void LifeTimeInterval::addRange(int from, int to) { Q_ASSERT(from > 0); Q_ASSERT(to > 0); Q_ASSERT(to >= from); if (_ranges.isEmpty()) { _ranges.prepend(LifeTimeIntervalRange(from, to)); _end = to; return; } LifeTimeIntervalRange *p = &_ranges.first(); if (to + 1 >= p->start && p->end + 1 >= from) { p->start = qMin(p->start, from); p->end = qMax(p->end, to); while (_ranges.count() > 1) { LifeTimeIntervalRange *p1 = p + 1; if (p->end + 1 < p1->start || p1->end + 1 < p->start) break; p1->start = qMin(p->start, p1->start); p1->end = qMax(p->end, p1->end); _ranges.remove(0); p = &_ranges.first(); } } else { if (to < p->start) { _ranges.prepend(LifeTimeIntervalRange(from, to)); } else { Q_ASSERT(from > _ranges.last().end); _ranges.push_back(LifeTimeIntervalRange(from, to)); } } _end = _ranges.last().end; } LifeTimeInterval LifeTimeInterval::split(int atPosition, int newStart) { Q_ASSERT(atPosition < newStart || newStart == InvalidPosition); Q_ASSERT(atPosition <= _end); Q_ASSERT(newStart <= _end || newStart == InvalidPosition); if (_ranges.isEmpty() || atPosition < _ranges.first().start) return LifeTimeInterval(); LifeTimeInterval newInterval = *this; newInterval.setSplitFromInterval(true); // search where to split the interval for (int i = 0, ei = _ranges.size(); i < ei; ++i) { if (_ranges.at(i).start <= atPosition) { if (_ranges.at(i).end >= atPosition) { // split happens in the middle of a range. Keep this range in both the old and the // new interval, and correct the end/start later _ranges.resize(i + 1); newInterval._ranges.remove(0, i); break; } } else { // split happens between two ranges. _ranges.resize(i); newInterval._ranges.remove(0, i); break; } } if (newInterval._ranges.first().end == atPosition) newInterval._ranges.remove(0); if (newStart == InvalidPosition) { // the temp stays inactive for the rest of its lifetime newInterval = LifeTimeInterval(); } else { // find the first range where the temp will get active again: while (!newInterval._ranges.isEmpty()) { const LifeTimeIntervalRange &range = newInterval._ranges.first(); if (range.start > newStart) { // The split position is before the start of the range. Either we managed to skip // over the correct range, or we got an invalid split request. Either way, this // Should Never Happen . Q_ASSERT(range.start > newStart); return LifeTimeInterval(); } else if (range.start <= newStart && range.end >= newStart) { // yay, we found the range that should be the new first range in the new interval! break; } else { // the temp stays inactive for this interval, so remove it. newInterval._ranges.remove(0); } } Q_ASSERT(!newInterval._ranges.isEmpty()); newInterval._ranges.first().start = newStart; _end = newStart; } // if we're in the middle of a range, set the end to the split position if (_ranges.last().end > atPosition) _ranges.last().end = atPosition; validate(); newInterval.validate(); return newInterval; } void LifeTimeInterval::dump(QTextStream &out) const { IRPrinter(&out).print(const_cast(&_temp)); out << ": ends at " << _end << " with ranges "; if (_ranges.isEmpty()) out << "(none)"; for (int i = 0; i < _ranges.size(); ++i) { if (i > 0) out << ", "; out << _ranges[i].start << " - " << _ranges[i].end; } if (_reg != InvalidRegister) out << " (register " << _reg << ")"; } bool LifeTimeInterval::lessThanForTemp(const LifeTimeInterval *r1, const LifeTimeInterval *r2) { return r1->temp() < r2->temp(); } LifeTimeIntervals::LifeTimeIntervals(IR::Function *function) : _basicBlockPosition(function->basicBlockCount()) , _positionForStatement(function->statementCount(), IR::Stmt::InvalidId) , _lastPosition(0) { _intervals.reserve(function->tempCount + 32); // we reserve a bit more space for intervals, because the register allocator will add intervals with fixed ranges for each register. renumber(function); } // Renumbering works as follows: // - phi statements are not numbered // - statement numbers start at 0 (zero) and increment get an even number (lastPosition + 2) // - basic blocks start at firstStatementNumber - 1, or rephrased: lastPosition + 1 // - basic blocks end at the number of the last statement // And during life-time calculation the next rule is used: // - any temporary starts its life-time at definingStatementPosition + 1 // // This numbering simulates half-open intervals. For example: // 0: %1 = 1 // 2: %2 = 2 // 4: %3 = %1 + %2 // 6: print(%3) // Here the half-open life-time intervals would be: // %1: (0-4] // %2: (2-4] // %3: (4-6] // Instead, we use the even statement positions for uses of temporaries, and the odd positions for // their definitions: // %1: [1-4] // %2: [3-4] // %3: [5-6] // This has the nice advantage that placing %3 (for example) is really easy: the start will // never overlap with the end of the uses of the operands used in the defining statement. // // The reason to start a basic-block at firstStatementPosition - 1 is to have correct start // positions for target temporaries of phi-nodes. Those temporaries will now start before the // first statement. This also means that any moves that get generated when transforming out of SSA // form, will not interfere with (read: overlap) any defining statements in the preceding // basic-block. void LifeTimeIntervals::renumber(IR::Function *function) { for (BasicBlock *bb : function->basicBlocks()) { if (bb->isRemoved()) continue; _basicBlockPosition[bb->index()].start = _lastPosition + 1; for (Stmt *s : bb->statements()) { if (s->asPhi()) continue; _lastPosition += 2; _positionForStatement[s->id()] = _lastPosition; } _basicBlockPosition[bb->index()].end = _lastPosition; } } LifeTimeIntervals::~LifeTimeIntervals() { qDeleteAll(_intervals); } Optimizer::Optimizer(IR::Function *function) : function(function) , inSSA(false) {} void Optimizer::run(QQmlEnginePrivate *qmlEngine, bool doTypeInference, bool peelLoops) { showMeTheCode(function, "Before running the optimizer"); cleanupBasicBlocks(function); function->removeSharedExpressions(); int statementCount = 0; for (BasicBlock *bb : function->basicBlocks()) if (!bb->isRemoved()) statementCount += bb->statementCount(); // showMeTheCode(function); static bool doSSA = qEnvironmentVariableIsEmpty("QV4_NO_SSA"); if (!function->hasTry && !function->hasWith && !function->module->debugMode && doSSA && statementCount <= 300) { // qout << "SSA for " << (function->name ? qPrintable(*function->name) : "") << endl; mergeBasicBlocks(function, nullptr, nullptr); ConvertArgLocals(function).toTemps(); showMeTheCode(function, "After converting arguments to locals"); // Calculate the dominator tree: DominatorTree df(function); { // This is in a separate scope, because loop-peeling doesn't (yet) update the LoopInfo // calculated by the LoopDetection. So by putting it in a separate scope, it is not // available after peeling. LoopDetection loopDetection(df); loopDetection.run(function); showMeTheCode(function, "After loop detection"); // cfg2dot(function, loopDetection.allLoops()); // ### disable loop peeling for now. It doesn't give any measurable performance // improvements at this time, but significantly increases the size of the // JIT generated code Q_UNUSED(peelLoops); if (0 && peelLoops) { QVector innerLoops = loopDetection.innermostLoops(); LoopPeeling(df).run(innerLoops); // cfg2dot(function, loopDetection.allLoops()); showMeTheCode(function, "After loop peeling"); if (!innerLoops.isEmpty()) verifyImmediateDominators(df, function); } } verifyCFG(function); verifyNoPointerSharing(function); df.computeDF(); verifyCFG(function); verifyImmediateDominators(df, function); DefUses defUses(function); // qout << "Converting to SSA..." << endl; convertToSSA(function, df, defUses); // showMeTheCode(function); // defUses.dump(); // qout << "Cleaning up phi nodes..." << endl; cleanupPhis(defUses); showMeTheCode(function, "After cleaning up phi-nodes"); StatementWorklist worklist(function); if (doTypeInference) { // qout << "Running type inference..." << endl; TypeInference(qmlEngine, defUses).run(worklist); showMeTheCode(function, "After type inference"); // qout << "Doing reverse inference..." << endl; ReverseInference(defUses).run(function); // showMeTheCode(function); // qout << "Doing type propagation..." << endl; TypePropagation(defUses).run(function, worklist); // showMeTheCode(function); verifyNoPointerSharing(function); } static const bool doOpt = qEnvironmentVariableIsEmpty("QV4_NO_OPT"); if (doOpt) { // qout << "Running SSA optimization..." << endl; worklist.reset(); optimizeSSA(worklist, defUses, df); showMeTheCode(function, "After optimization"); verifyImmediateDominators(df, function); verifyCFG(function); } verifyNoPointerSharing(function); mergeBasicBlocks(function, &defUses, &df); verifyImmediateDominators(df, function); verifyCFG(function); // Basic-block cycles that are unreachable (i.e. for loops in a then-part where the // condition is calculated to be always false) are not yet removed. This will choke the // block scheduling, so remove those now. // qout << "Cleaning up unreachable basic blocks..." << endl; cleanupBasicBlocks(function); // showMeTheCode(function); verifyImmediateDominators(df, function); verifyCFG(function); // Transform the CFG into edge-split SSA. showMeTheCode(function, "Before edge splitting"); splitCriticalEdges(function, df, worklist, defUses); showMeTheCode(function, "After edge splitting"); verifyImmediateDominators(df, function); verifyCFG(function); // qout << "Doing block scheduling..." << endl; // df.dumpImmediateDominators(); startEndLoops = BlockScheduler(function, df).go(); showMeTheCode(function, "After basic block scheduling"); // cfg2dot(function); #ifndef QT_NO_DEBUG checkCriticalEdges(function->basicBlocks()); #endif if (!function->module->debugMode) { RemoveLineNumbers::run(function); showMeTheCode(function, "After line number removal"); } // qout << "Finished SSA." << endl; inSSA = true; } else { removeUnreachleBlocks(function); inSSA = false; } } void Optimizer::convertOutOfSSA() { if (!inSSA) return; // There should be no critical edges at this point. for (BasicBlock *bb : function->basicBlocks()) { MoveMapping moves; for (BasicBlock *successor : bb->out) { const int inIdx = successor->in.indexOf(bb); Q_ASSERT(inIdx >= 0); for (Stmt *s : successor->statements()) { if (Phi *phi = s->asPhi()) { moves.add(clone(phi->incoming[inIdx], function), clone(phi->targetTemp, function)->asTemp()); } else { break; } } } if (DebugMoveMapping) { QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream os(&buf); os << "Move mapping for function "; if (function->name) os << *function->name; else os << (void *) function; os << " on basic-block L" << bb->index() << ":" << endl; moves.dump(); buf.close(); qDebug("%s", buf.data().constData()); } moves.order(); moves.insertMoves(bb, function, true); } for (BasicBlock *bb : function->basicBlocks()) { while (!bb->isEmpty()) { if (bb->statements().first()->asPhi()) { bb->removeStatement(0); } else { break; } } } } LifeTimeIntervals::Ptr Optimizer::lifeTimeIntervals() const { Q_ASSERT(isInSSA()); LifeRanges lifeRanges(function, startEndLoops); // lifeRanges.dump(); // showMeTheCode(function); return lifeRanges.intervals(); } static int countPhis(BasicBlock *bb) { int count = 0; for (Stmt *s : bb->statements()) { if (s->isa()) ++count; else break; } return count; } // Basic blocks can have only 1 terminator. This function returns a bit vector, where a 1 on a // certain index indicates that the terminator (jump) at the end of the basic block with that index // can be omitted. BitVector Optimizer::calculateOptionalJumps() { const int maxSize = function->basicBlockCount(); BitVector optional(maxSize, false); if (maxSize < 2) return optional; BitVector reachableWithoutJump(maxSize, false); for (int i = maxSize - 1; i >= 0; --i) { BasicBlock *bb = function->basicBlock(i); if (bb->isRemoved()) continue; if (Jump *jump = bb->statements().last()->asJump()) { if (reachableWithoutJump.at(jump->target->index())) { if (bb->statements().size() - countPhis(bb)> 1) reachableWithoutJump.clear(); optional.setBit(bb->index()); reachableWithoutJump.setBit(bb->index()); continue; } } reachableWithoutJump.clear(); reachableWithoutJump.setBit(bb->index()); } return optional; } void Optimizer::showMeTheCode(IR::Function *function, const char *marker) { ::showMeTheCode(function, marker); } static inline bool overlappingStorage(const Temp &t1, const Temp &t2) { // This is the same as the operator==, but for one detail: memory locations are not sensitive // to types, and neither are general-purpose registers. if (t1.index != t2.index) return false; // different position, where-ever that may (physically) be. if (t1.kind != t2.kind) return false; // formal/local/(physical-)register/stack do never overlap if (t1.kind != Temp::PhysicalRegister) // Other than registers, ... return t1.kind == t2.kind; // ... everything else overlaps: any memory location can hold everything. // So now the index is the same, and we know that both stored in a register. If both are // floating-point registers, they are the same. Or, if both are non-floating-point registers, // generally called general-purpose registers, they are also the same. return (t1.type == DoubleType && t2.type == DoubleType) || (t1.type != DoubleType && t2.type != DoubleType); } MoveMapping::Moves MoveMapping::sourceUsages(Expr *e, const Moves &moves) { Moves usages; if (Temp *t = e->asTemp()) { for (int i = 0, ei = moves.size(); i != ei; ++i) { const Move &move = moves[i]; if (Temp *from = move.from->asTemp()) if (overlappingStorage(*from, *t)) usages.append(move); } } return usages; } void MoveMapping::add(Expr *from, Temp *to) { if (Temp *t = from->asTemp()) { if (overlappingStorage(*t, *to)) { // assignments like fp1 = fp1 or var{&1} = double{&1} can safely be skipped. if (DebugMoveMapping) { QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream os(&buf); IRPrinter printer(&os); os << "Skipping "; printer.print(to); os << " <- "; printer.print(from); buf.close(); qDebug("%s", buf.data().constData()); } return; } } Move m(from, to); if (_moves.contains(m)) return; _moves.append(m); } // Order the moves that are generated when resolving edges during register allocation (see [Wimmer1] // section 6 for details). Now these moves form one or more graphs, so we have to output them in // such an order that values don't get overwritten: // r1 <- r0 // r2 <- r1 // That input has to be ordered as follows in order to prevent the value in r1 from being lost: // r2 <- r1 // r1 <- r0 // // So, the algorithm is to output the leaves first, and take them out of the input. This will result // in some moves to become leaves (in the above example: when leaf r2 <- r1 is generated and taken // away, the r1 <- r0 is now a leaf), so we can output those and take those out, and repeat until // there are no more leafs. // // The tricky part is that there might be cycles: // r4 <- r5 // r5 <- r4 // These have to be turned into a "register swap": // r4 <=> r5 // // So after running the above algorithm where we progressively remove the leaves, we are left with // zero or more cycles. To resolve those, we break one of the edges of the cycle, and for all other // edges we generate swaps. Note that the swaps will always occur as the last couple of moves, // because otherwise they might clobber sources for moves: // r4 <=> r5 // r6 <- r5 // Here, the value of r5 is already overwritten with the one in r4, so the correct order is: // r6 <- r5 // r4 <=> r5 void MoveMapping::order() { QList output; output.reserve(_moves.size()); while (!_moves.isEmpty()) { // Take out all leaf edges, because we can output them without any problems. int nextLeaf = findLeaf(); if (nextLeaf == -1) break; // No more leafs left, we're done here. output.append(_moves.takeAt(nextLeaf)); // Now there might be new leaf edges: any move that had the input of the previously found // leaf as an output, so loop around. } while (!_moves.isEmpty()) { // We're now left with one or more cycles. // Step one: break the/a cycle. _moves.removeFirst(); // Step two: find the other edges of the cycle, starting with the one of that is now a leaf. while (!_moves.isEmpty()) { int nextLeaf = findLeaf(); if (nextLeaf == -1) break; // We're done with this cycle. Move m = _moves.takeAt(nextLeaf); // Step three: get the edges from the cycle and turn it into a swap m.needsSwap = true; output.append(m); // Because we took out a leaf, find the next one. } // We're done with the cycle, let's see if there are more. } _moves = output; } int MoveMapping::findLeaf() const { for (int i = 0, e = _moves.size(); i != e; ++i) { // Take an edge from the list... const Temp *target = _moves.at(i).to; // ... and see if its target is used as a source... bool targetUsedAsSource = false; for (int j = 0; j != e; ++j) { if (i == j) continue; Expr *source = _moves.at(j).from; if (const Temp *sourceTemp = source->asTemp()) { if (overlappingStorage(*target, *sourceTemp)) { targetUsedAsSource = true; break; } } } // ... if not, we have a leaf edge ... if (!targetUsedAsSource) return i; // .. otherwise we try the next one. } return -1; // No leaf found } QList MoveMapping::insertMoves(BasicBlock *bb, IR::Function *function, bool atEnd) const { QList newMoves; newMoves.reserve(_moves.size()); int insertionPoint = atEnd ? bb->statements().size() - 1 : 0; for (const Move &m : _moves) { IR::Move *move = function->NewStmt(); move->init(clone(m.to, function), clone(m.from, function)); move->swap = m.needsSwap; bb->insertStatementBefore(insertionPoint++, move); newMoves.append(move); } return newMoves; } void MoveMapping::dump() const { if (DebugMoveMapping) { QBuffer buf; buf.open(QIODevice::WriteOnly); QTextStream os(&buf); IRPrinter printer(&os); os << "Move mapping has " << _moves.size() << " moves..." << endl; for (const Move &m : _moves) { os << "\t"; printer.print(m.to); if (m.needsSwap) os << " <-> "; else os << " <-- "; printer.print(m.from); os << endl; } qDebug("%s", buf.data().constData()); } } // References: // [Wimmer1] C. Wimmer and M. Franz. Linear Scan Register Allocation on SSA Form. In Proceedings of // CGO'10, ACM Press, 2010 // [Wimmer2] C. Wimmer and H. Mossenbock. Optimized Interval Splitting in a Linear Scan Register // Allocator. In Proceedings of the ACM/USENIX International Conference on Virtual // Execution Environments, pages 132-141. ACM Press, 2005. // [Briggs] P. Briggs, K.D. Cooper, T.J. Harvey, and L.T. Simpson. Practical Improvements to the // Construction and Destruction of Static Single Assignment Form. // [Appel] A.W. Appel. Modern Compiler Implementation in Java. Second edition, Cambridge // University Press. // [Ramalingam] G. Ramalingam and T. Reps. An Incremental Algorithm for Maintaining the Dominator // Tree of a Reducible Flowgraph.