/*
 * Copyright (C) 2013-2019 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

#include <wtf/Dominators.h>

namespace WTF {

template<typename Graph>
class NaturalLoops;

template<typename Graph>
class NaturalLoop {
    WTF_MAKE_FAST_ALLOCATED;
public:
    NaturalLoop()
        : m_graph(nullptr)
        , m_header(nullptr)
        , m_outerLoopIndex(UINT_MAX)
    {
    }

    NaturalLoop(Graph& graph, typename Graph::Node header, unsigned index)
        : m_graph(&graph)
        , m_header(header)
        , m_outerLoopIndex(UINT_MAX)
        , m_index(index)
    {
    }

    Graph* graph() const { return m_graph; }

    typename Graph::Node header() const { return m_header; }

    unsigned size() const { return m_body.size(); }
    typename Graph::Node at(unsigned i) const { return m_body[i]; }
    typename Graph::Node operator[](unsigned i) const { return at(i); }

    // This is the slower, but simpler, way of asking if a block belongs to
    // a natural loop. It's faster to call NaturalLoops::belongsTo(), which
    // tries to be O(loop depth) rather than O(loop size). Loop depth is
    // almost always smaller than loop size. A *lot* smaller.
    bool contains(typename Graph::Node block) const
    {
        for (unsigned i = m_body.size(); i--;) {
            if (m_body[i] == block)
                return true;
        }
        ASSERT(block != header()); // Header should be contained.
        return false;
    }

    // The index of this loop in NaturalLoops.
    unsigned index() const { return m_index; }

    bool isOuterMostLoop() const { return m_outerLoopIndex == UINT_MAX; }

    void dump(PrintStream& out) const
    {
        if (!m_graph) {
            out.print("<null>");
            return;
        }

        out.print("[Header: ", m_graph->dump(header()), ", Body:");
        for (unsigned i = 0; i < m_body.size(); ++i)
            out.print(" ", m_graph->dump(m_body[i]));
        out.print("]");
    }

private:
    template<typename>
    friend class NaturalLoops;

    void addBlock(typename Graph::Node block)
    {
        ASSERT(!m_body.contains(block)); // The NaturalLoops algorithm relies on blocks being unique in this vector.
        m_body.append(block);
    }

    Graph* m_graph;
    typename Graph::Node m_header;
    Vector<typename Graph::Node, 4> m_body;
    unsigned m_outerLoopIndex;
    unsigned m_index;
};

template<typename Graph>
class NaturalLoops {
    WTF_MAKE_FAST_ALLOCATED;
public:
    typedef std::array<unsigned, 2> InnerMostLoopIndices;

    NaturalLoops(Graph& graph, Dominators<Graph>& dominators, bool selfCheck = false)
        : m_graph(graph)
        , m_innerMostLoopIndices(graph.template newMap<InnerMostLoopIndices>())
    {
        // Implement the classic dominator-based natural loop finder. The first
        // step is to find all control flow edges A -> B where B dominates A.
        // Then B is a loop header and A is a backward branching block. We will
        // then accumulate, for each loop header, multiple backward branching
        // blocks. Then we backwards graph search from the backward branching
        // blocks to their loop headers, which gives us all of the blocks in the
        // loop body.

        static constexpr bool verbose = false;

        if (verbose) {
            dataLog("Dominators:\n");
            dominators.dump(WTF::dataFile());
        }

        m_loops.shrink(0);

        for (unsigned blockIndex = graph.numNodes(); blockIndex--;) {
            typename Graph::Node header = graph.node(blockIndex);
            if (!header)
                continue;

            for (unsigned i = graph.predecessors(header).size(); i--;) {
                typename Graph::Node footer = graph.predecessors(header)[i];
                if (!dominators.dominates(header, footer))
                    continue;
                // At this point, we've proven 'header' is actually a loop header and
                // that 'footer' is a loop footer.
                bool found = false;
                for (unsigned j = m_loops.size(); j--;) {
                    if (m_loops[j].header() == header) {
                        m_loops[j].addBlock(footer);
                        found = true;
                        break;
                    }
                }
                if (found)
                    continue;
                NaturalLoop<Graph> loop(graph, header, m_loops.size());
                loop.addBlock(footer);
                m_loops.append(loop);
            }
        }

        if (verbose)
            dataLog("After bootstrap: ", *this, "\n");

        FastBitVector seenBlocks;
        Vector<typename Graph::Node, 4> blockWorklist;
        seenBlocks.resize(graph.numNodes());

        for (unsigned i = m_loops.size(); i--;) {
            NaturalLoop<Graph>& loop = m_loops[i];

            seenBlocks.clearAll();
            ASSERT(blockWorklist.isEmpty());

            if (verbose)
                dataLog("Dealing with loop ", loop, "\n");

            for (unsigned j = loop.size(); j--;) {
                seenBlocks[graph.index(loop[j])] = true;
                blockWorklist.append(loop[j]);
            }

            while (!blockWorklist.isEmpty()) {
                typename Graph::Node block = blockWorklist.takeLast();

                if (verbose)
                    dataLog("    Dealing with ", graph.dump(block), "\n");

                if (block == loop.header())
                    continue;

                for (unsigned j = graph.predecessors(block).size(); j--;) {
                    typename Graph::Node predecessor = graph.predecessors(block)[j];
                    if (seenBlocks[graph.index(predecessor)])
                        continue;

                    loop.addBlock(predecessor);
                    blockWorklist.append(predecessor);
                    seenBlocks[graph.index(predecessor)] = true;
                }
            }
        }

        // Figure out reverse mapping from blocks to loops.
        for (unsigned blockIndex = graph.numNodes(); blockIndex--;) {
            typename Graph::Node block = graph.node(blockIndex);
            if (!block)
                continue;
            for (unsigned i = std::tuple_size<InnerMostLoopIndices>::value; i--;)
                m_innerMostLoopIndices[block][i] = UINT_MAX;
        }
        for (unsigned loopIndex = m_loops.size(); loopIndex--;) {
            NaturalLoop<Graph>& loop = m_loops[loopIndex];

            for (unsigned blockIndexInLoop = loop.size(); blockIndexInLoop--;) {
                typename Graph::Node block = loop[blockIndexInLoop];

                for (unsigned i = 0; i < std::tuple_size<InnerMostLoopIndices>::value; ++i) {
                    unsigned thisIndex = m_innerMostLoopIndices[block][i];
                    if (thisIndex == UINT_MAX || loop.size() < m_loops[thisIndex].size()) {
                        insertIntoBoundedVector(
                            m_innerMostLoopIndices[block], std::tuple_size<InnerMostLoopIndices>::value,
                            loopIndex, i);
                        break;
                    }
                }
            }
        }

        // Now each block knows its inner-most loop and its next-to-inner-most loop. Use
        // this to figure out loop parenting.
        for (unsigned i = m_loops.size(); i--;) {
            NaturalLoop<Graph>& loop = m_loops[i];
            RELEASE_ASSERT(m_innerMostLoopIndices[loop.header()][0] == i);

            loop.m_outerLoopIndex = m_innerMostLoopIndices[loop.header()][1];
        }

        if (selfCheck) {
            // Do some self-verification that we've done some of this correctly.

            for (unsigned blockIndex = graph.numNodes(); blockIndex--;) {
                typename Graph::Node block = graph.node(blockIndex);
                if (!block)
                    continue;

                Vector<const NaturalLoop<Graph>*> simpleLoopsOf;

                for (unsigned i = m_loops.size(); i--;) {
                    if (m_loops[i].contains(block))
                        simpleLoopsOf.append(&m_loops[i]);
                }

                Vector<const NaturalLoop<Graph>*> fancyLoopsOf = loopsOf(block);

                std::sort(simpleLoopsOf.begin(), simpleLoopsOf.end());
                std::sort(fancyLoopsOf.begin(), fancyLoopsOf.end());

                RELEASE_ASSERT(simpleLoopsOf == fancyLoopsOf);
            }
        }

        if (verbose)
            dataLog("Results: ", *this, "\n");
    }

    Graph& graph() { return m_graph; }

    unsigned numLoops() const
    {
        return m_loops.size();
    }
    const NaturalLoop<Graph>& loop(unsigned i) const
    {
        return m_loops[i];
    }

    // Return either null if the block isn't a loop header, or the
    // loop it belongs to.
    const NaturalLoop<Graph>* headerOf(typename Graph::Node block) const
    {
        const NaturalLoop<Graph>* loop = innerMostLoopOf(block);
        if (!loop)
            return nullptr;
        if (loop->header() == block)
            return loop;
        if (ASSERT_ENABLED) {
            for (; loop; loop = innerMostOuterLoop(*loop))
                ASSERT(loop->header() != block);
        }
        return nullptr;
    }

    const NaturalLoop<Graph>* innerMostLoopOf(typename Graph::Node block) const
    {
        unsigned index = m_innerMostLoopIndices[block][0];
        if (index == UINT_MAX)
            return nullptr;
        return &m_loops[index];
    }

    const NaturalLoop<Graph>* innerMostOuterLoop(const NaturalLoop<Graph>& loop) const
    {
        if (loop.m_outerLoopIndex == UINT_MAX)
            return nullptr;
        return &m_loops[loop.m_outerLoopIndex];
    }

    bool belongsTo(typename Graph::Node block, const NaturalLoop<Graph>& candidateLoop) const
    {
        // It's faster to do this test using the loop itself, if it's small.
        if (candidateLoop.size() < 4)
            return candidateLoop.contains(block);

        for (const NaturalLoop<Graph>* loop = innerMostLoopOf(block); loop; loop = innerMostOuterLoop(*loop)) {
            if (loop == &candidateLoop)
                return true;
        }
        return false;
    }

    unsigned loopDepth(typename Graph::Node block) const
    {
        unsigned depth = 0;
        for (const NaturalLoop<Graph>* loop = innerMostLoopOf(block); loop; loop = innerMostOuterLoop(*loop))
            depth++;
        return depth;
    }

    // Return all loops this belongs to. The first entry in the vector is the innermost loop. The last is the
    // outermost loop.
    Vector<const NaturalLoop<Graph>*> loopsOf(typename Graph::Node block) const
    {
        Vector<const NaturalLoop<Graph>*> result;
        for (const NaturalLoop<Graph>* loop = innerMostLoopOf(block); loop; loop = innerMostOuterLoop(*loop))
            result.append(loop);
        return result;
    }

    void dump(PrintStream& out) const
    {
        out.print("NaturalLoops:{");
        CommaPrinter comma;
        for (unsigned i = 0; i < m_loops.size(); ++i)
            out.print(comma, m_loops[i]);
        out.print("}");
    }

private:
    Graph& m_graph;

    // If we ever had a scalability problem in our natural loop finder, we could
    // use some HashMap's here. But it just feels a heck of a lot less convenient.
    Vector<NaturalLoop<Graph>, 4> m_loops;

    typename Graph::template Map<InnerMostLoopIndices> m_innerMostLoopIndices;
};

} // namespace WTF

using WTF::NaturalLoop;
using WTF::NaturalLoops;
