/****************************************************************************
**
** 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<T>::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 <QtCore/QBuffer>
#include <QtCore/QCoreApplication>
#include <QtCore/QStringList>
#include <QtCore/QSet>
#include <QtCore/QLinkedList>
#include <QtCore/QStack>
#include <qv4runtime_p.h>
#include <cmath>
#include <iostream>
#include <cassert>

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<int, 8> 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<int, 8>::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<BasicBlock *> &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<int> dfnum; // BasicBlock index -> dfnum
        std::vector<int> vertex;
        std::vector<BasicBlockIndex> parent; // BasicBlock index -> parent BasicBlock index
        std::vector<BasicBlockIndex> ancestor; // BasicBlock index -> ancestor BasicBlock index
        std::vector<BasicBlockIndex> best; // BasicBlock index -> best BasicBlock index
        std::vector<BasicBlockIndex> semi; // BasicBlock index -> semi dominator BasicBlock index
        std::vector<BasicBlockIndex> samedom; // BasicBlock index -> same dominator BasicBlock index

        Data(): N(0) {}
    };

    IR::Function *function;
    QScopedPointer<Data> d;
    std::vector<BasicBlockIndex> idom; // BasicBlock index -> immediate dominator BasicBlock index
    std::vector<BasicBlockSet> 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<DFSTodo> 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<BasicBlockIndex> &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<int>(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<int>(bbCount, InvalidBasicBlockIndex);
        d->parent = std::vector<int>(bbCount, InvalidBasicBlockIndex);
        d->dfnum = std::vector<int>(size_t(bbCount), 0);
        d->semi = std::vector<BasicBlockIndex>(bbCount, InvalidBasicBlockIndex);
        d->ancestor = std::vector<BasicBlockIndex>(bbCount, InvalidBasicBlockIndex);
        idom = std::vector<BasicBlockIndex>(bbCount, InvalidBasicBlockIndex);
        d->samedom = std::vector<BasicBlockIndex>(bbCount, InvalidBasicBlockIndex);
        d->best = std::vector<BasicBlockIndex>(bbCount, InvalidBasicBlockIndex);

        QHash<BasicBlockIndex, std::vector<BasicBlockIndex> > bucket;
        bucket.reserve(bbCount);

        DFS(function->basicBlock(0)->index());
        Q_ASSERT(d->N == function->liveBasicBlocksCount());

        std::vector<BasicBlockIndex> 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<BasicBlockIndex> children;
        std::vector<BasicBlockIndex> 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<std::vector<BasicBlockIndex> > 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<NodeProgress> nodeStatus;
        nodeStatus.resize(function->basicBlockCount());
        std::vector<BasicBlockIndex> 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<BasicBlockIndex>::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<std::vector<BasicBlockIndex>::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<int> *nodeDepths;
        Cmp(std::vector<int> *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<BasicBlock *> calculateDFNodeIterOrder() const
    {
        std::vector<int> depths = calculateNodeDepths();
        QVector<BasicBlock *> 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<int> calculateNodeDepths() const
    {
        std::vector<int> 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<int> &nodeDepths) const
    {
        std::vector<int> worklist;
        worklist.reserve(8);
        int depth = -1;

        do {
            worklist.push_back(nodeIdx);
            nodeIdx = idom[nodeIdx];
            depth = nodeDepths[nodeIdx];
        } while (depth == -1);

        for (std::vector<int>::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<BasicBlockIndex> 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<BasicBlockIndex> 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<BasicBlockIndex> &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<BasicBlockIndex> &bestPrefix, const std::vector<BasicBlockIndex> &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<Temp> _allTemps;
    std::vector<BasicBlockSet> _defsites;
    std::vector<std::vector<int> > 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<int> &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<Temp> &allTemps() const
    { return _allTemps; }

    void collectDefSites(const Temp &n, std::vector<BasicBlock *> &bbs) const {
        Q_ASSERT(!n.isInvalid());
        Q_ASSERT(n.index < _defsites.size());
        _defsites[n.index].collectValues(bbs);
    }

    const std::vector<int> &inBlock(BasicBlock *n) const
    {
        return A_orig.at(n->index());
    }

    bool isNonLocal(const Temp &var) const
    {
        Q_ASSERT(!var.isInvalid());
        Q_ASSERT(static_cast<int>(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<Stmt *> uses;

        bool isValid() const
        { return temp.kind != Temp::Invalid; }

        void clear()
        { defStmt = 0; blockOfStatement = 0; uses.clear(); }
    };

private:
    std::vector<DefUse> _defUses;
    typedef QVarLengthArray<Temp, 4> Temps;
    std::vector<Temps> _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<unsigned>(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<UntypedTemp> defsUntyped() const
    {
        QVector<UntypedTemp> res;
        res.reserve(tempCount());
        for (const DefUse &du : _defUses)
            if (du.isValid())
                res.append(UntypedTemp(du.temp));
        return res;
    }

    std::vector<const Temp *> defs() const {
        std::vector<const Temp *> 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<unsigned>(variable.index) < _defUses.size());
        _defUses[variable.index].clear();
    }

    void addUses(const Temp &variable, const QVector<Stmt *> &newUses)
    {
        Q_ASSERT(static_cast<unsigned>(variable.index) < _defUses.size());
        QVector<Stmt *> &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<Stmt *> &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<unsigned>(variable.index) < _defUses.size());
        return _defUses[variable.index].uses.size();
    }

    Stmt *defStmt(const Temp &variable) const
    {
        Q_ASSERT(static_cast<unsigned>(variable.index) < _defUses.size());
        return _defUses[variable.index].defStmt;
    }

    BasicBlock *defStmtBlock(const Temp &variable) const
    {
        Q_ASSERT(static_cast<unsigned>(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<unsigned>(var.index) < _defUses.size());
        QVector<Stmt *> &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<unsigned>(s->id()) < _usesPerStatement.size());
        return _usesPerStatement[s->id()];
    }

    const QVector<Stmt *> &uses(const Temp &var) const
    {
        return _defUses[var.index].uses;
    }

    QVector<Stmt*> removeDefUses(Stmt *s)
    {
        QVector<Stmt*> 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<Phi>();
    phiNode->targetTemp = f->New<Temp>();
    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<Temp>();
        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<int> 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<TodoAction> 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"<<a<<"with"<<newTmp<<"in L"<<Y->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"<<a<<"with"<<newIdx;
        t->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<BitVector > 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<BasicBlock *> 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<int> &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<Phi *> 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<Stmt *> stmts;
    BitVector worklist;
    unsigned worklistSize;
    std::vector<int> replaced;
    BitVector removed;

    Q_DISABLE_COPY(StatementWorklist)

public:
    StatementWorklist(IR::Function *function)
        : theFunction(function)
        , stmts(function->statementCount(), static_cast<Stmt *>(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<unsigned>(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<unsigned>(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<Stmt *> &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<size_t>(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<unsigned>(s->id()) >= stmts.size()) {
            if (static_cast<unsigned>(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<int>(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<Temp *, 8> _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<Type>(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<DiscoveredType> 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<UntypedTemp> knownOk;
        QVector<UntypedTemp> 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<UntypedTemp> &knownOk) const
    {
        const QVector<Stmt *> &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<Conversion> _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<Move>();
                    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<Move>();
                    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<Move>();
                    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>();
                    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<BasicBlock *> 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<Jump>();
            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<BasicBlock *> loopBody;
        QVector<LoopInfo *> 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<BasicBlock *> 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<LoopInfo *> allLoops() const
    { return loopInfos; }

    // returns all loop headers for loops that have no nested loops.
    QVector<LoopInfo *> innermostLoops() const
    {
        QVector<LoopInfo *> 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<BasicBlock *> &backedges)
    {
        loopHead->markAsGroupStart();
        LoopInfo *info = new LoopInfo;
        info->loopHeader = loopHead;
        loopInfos.append(info);

        std::vector<BasicBlock *> 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<LoopInfo *> 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<BasicBlock *> postponed;

        WorkForGroup(BasicBlock *group = 0): group(group) {}
    };
    WorkForGroup currentGroup;
    QStack<WorkForGroup> postponedGroups;
    QVector<BasicBlock *> sequence;
    ProcessedBlocks emitted;
    QHash<BasicBlock *, BasicBlock *> 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<BasicBlock *, BasicBlock *> 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<BasicBlock *> &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<BasicBlock *, 16> 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<Stmt *> *newUses = 0)
    {
        Q_ASSERT(replacement->asTemp() || replacement->asConst() || replacement->asName());

        qSwap(_toReplace, toReplace);
        qSwap(_replacement, replacement);

        const QVector<Stmt *> &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<<endl;

        if (newUses)
            newUses->reserve(uses.size());

//        qout << "        " << uses.size() << " uses:"<<endl;
        for (Stmt *use : uses) {
//            qout<<"        ";use->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<BasicBlock *> 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<LoopDetection::LoopInfo *> &loops = QVector<LoopDetection::LoopInfo *>())
{
    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<Stmt *> 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>();
                        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<Const>();
                        c->init(SInt32Type, 0);
                        member->base = c;
                        continue;
                    }
                }

                // copy propagation:
                if (Temp *sourceTemp = m->source->asTemp()) {
                    QVector<Stmt *> 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<Jump>();
                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<Temp *> 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<int> 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<LiveRegs> _liveIn;
    std::vector<LifeTimeInterval *> _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 *&lti = _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<BasicBlock *, BasicBlock *> &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<Stmt *> &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 &lti = interval(opd);
                lti.setFrom(defPosition(s));
                live.remove(lti.temp().index);
                _sortedIntervals->add(&lti);
            }
            //### 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<BasicBlock *> 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<Stmt *> 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<ArgLocal>();
                source->init(ArgLocal::Formal, i, 0);

                Temp *target = function->New<Temp>();
                target->init(Temp::VirtualRegister, newTemp);

                Move *m = function->NewStmt<Move>();
                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<Temp>();
                t->init(Temp::VirtualRegister, fetchTempForLocal(al->index));
                e = t;
            } else if (convertArgs && al->kind == ArgLocal::Formal) {
                Temp *t = function->New<Temp>();
                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<int> tempForFormal;
    std::vector<int> 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<Phi>();
            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<Stmt *> stmts;
        QSet<Expr *> 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<LoopDetection::LoopInfo *> &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<BasicBlock *> &from, const QVector<BasicBlock *> &to, QVector<BasicBlock *> &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<BasicBlock *> 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 <TM>.
                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 *>(&_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) : "<anonymous>") << 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<LoopDetection::LoopInfo *> 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<Phi>())
            ++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<Move> 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<IR::Move *> MoveMapping::insertMoves(BasicBlock *bb, IR::Function *function, bool atEnd) const
{
    QList<IR::Move *> newMoves;
    newMoves.reserve(_moves.size());

    int insertionPoint = atEnd ? bb->statements().size() - 1 : 0;
    for (const Move &m : _moves) {
        IR::Move *move = function->NewStmt<IR::Move>();
        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.
