| /* |
| * 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_DISABLED) { |
| 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; |