mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
We already computed this information so this is just storing information we were already computing. One thing to note is that in code with canonicalized loops, we will always only have one backedge. But we would like loop region to be correct even in the case of non-canonicalized code so we support having multiple back edges. But since the common case is 1 backedge, we optimize for that case. This commit contains updated tests and also updates to the loop region graph viewer so that it draws backedges as green arrows from the loop to its backedge subregions. The test updates were done by examining each test case by hand.
1094 lines
42 KiB
C++
1094 lines
42 KiB
C++
//===--- LoopRegionAnalysis.h -----------------------------------*- C++ -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// Purpose
|
|
/// =======
|
|
///
|
|
/// This is an analysis that is an implementation of interval analysis in order
|
|
/// to allow for dataflow to be done in a structured way on the loop tree. The
|
|
/// facilities it provides are:
|
|
///
|
|
/// 1. An abstraction called ``LoopRegion`` that abstracts over natural loops,
|
|
/// basic blocks, and the top level function. This is meant to provide a
|
|
/// function level pointer for summarizing information.
|
|
///
|
|
/// 2. For each Region, if it has subregions (i.e. is not a Block), an RPO
|
|
/// ordering of the Subregions are provided. Loops in this RPO ordering have the
|
|
/// RPO number of their header.
|
|
///
|
|
/// 3. Natural loops are defined by the loops that SILLoopInfo can find. Thus it
|
|
/// is useful to be able to know about loops that are not able to be found by
|
|
/// SILLoopInfo (i.e. irreducible loops). This information is computed by
|
|
/// finding all backedges and any backedge that is not recognized by SILLoopInfo
|
|
/// as a backedge has its head and tail region marked as unknown control flow
|
|
/// boundaries. This can be queries via the methods:
|
|
///
|
|
/// - LoopRegion::isUnknownControlFlowEdgeHead().
|
|
/// - LoopRegion::isUnknownControlFlowEdgeTail().
|
|
///
|
|
/// 4. Only loops with single back edges are supported by this analysis. If such
|
|
/// a loop is detected, we mark the head, tail of the edge as irreducible
|
|
/// control flow boundaries. To handle such loops, we rely on loop
|
|
/// canonicalization to transform the multiple backedge loops into multiple
|
|
/// loops with singular backedges or by merging the backedge blocks.
|
|
///
|
|
/// Algorithm
|
|
/// =========
|
|
///
|
|
/// The main consideration here is that we want to ensure that we only traverse
|
|
/// the CFG once and reuse the information that is already provided via the loop
|
|
/// info analysis and the post order analysis.
|
|
///
|
|
/// We begin by visiting each block in RPO order. During this RPO traversal, we:
|
|
///
|
|
/// 1. Create LoopRegions for each block and wire up each block's predecessor
|
|
/// and successor blocks as predecessors/successors of the block's region.
|
|
///
|
|
/// 2. Detect any backedges that are unknown to SILLoopInfo and mark the blocks
|
|
/// that form the head/tail of the backedge as unknown control flow boundaries.
|
|
///
|
|
/// 3. We lookup/create the region for the innermost loop that contains the
|
|
/// block and add the block's region as a subregion of that loop.
|
|
///
|
|
/// Then we perform a postorder DFS of the loop nest. The postorder provides the
|
|
/// inductive rule that all loops will be visited after all of their subloops
|
|
/// have been visited. If a Loop has no subloops (i.e. all subregions are
|
|
/// blocks), we do nothing. Otherwise, if the loop does have subloops, we visit
|
|
/// each subloop and do the following:
|
|
///
|
|
/// 1. The region data structure of the header of the subloop still is the
|
|
/// successor of its predecessor outside of the subloop in the various block
|
|
/// datastructures. Rewire those regions to consider the loop to be their
|
|
/// successor instead and make each of those regions on of the predecessors of
|
|
/// the subloop. Then clear the predecessor list of the subloop's header
|
|
/// region. The header should have no predecessor edges after this operation
|
|
/// completes.
|
|
///
|
|
/// 2. For each of the exiting regions of the subloop (that is the regions
|
|
/// inside the loop that leave the subloop):
|
|
///
|
|
/// a. Replace each predecessor edge in the exiting block's successors with an
|
|
/// edge pointing at the subloop instead of at the exiting block and
|
|
/// vis-a-versa.
|
|
///
|
|
/// b. Introduce a loop-escape edge from the exiting regions that associates
|
|
/// the specific successor number of the region with the loop successor edge
|
|
/// that we just created. By using this indirection, if the loop is an inner
|
|
/// loop and further acts as an exit from an outer loop, the region's
|
|
/// successor edge will be able to be used to traverse from the region ->
|
|
/// inner loop -> outer loop successor. (i).
|
|
///
|
|
/// After this is complete, the exiting blocks should have no successor edges
|
|
/// going outside the loop. For each of the removed edges additionally we
|
|
/// introduce an "exiting edge". The reason this is necessary is to make it
|
|
/// easier to handle exits through
|
|
///
|
|
/// 3. If the loop has multiple back edges, mark each of the backedges as
|
|
/// unknown control flow boundaries.
|
|
///
|
|
/// We complete by performing the same operation on the top level function.
|
|
///
|
|
/// After this is done, each level of the loop hierarchy should be able to be
|
|
/// treated from a dataflow perspective as its own separate function with only
|
|
/// one caller, the parent loop region (or the top level function), enabling
|
|
/// extremely aggressive operations (or even outlining).
|
|
///
|
|
/// (i). The reason why this is important is to be able to handle exits to
|
|
/// trapping code. Trapping code will always be associated with the outer most
|
|
/// loop since it is an exit from all of the loops. Yet in ARC we need to be
|
|
/// able to find that region from For certain optimizations like ARC, we need to
|
|
/// be able to ignore these successor regions. By tracing up the loop nest
|
|
/// hierarchy by these loop exits, we can find the unreachable regions in the
|
|
/// parent and ignore them. This also allows us to be more precise about
|
|
/// hoisting retains/releases and avoid hoisting into such regions.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_LOOPNESTANALYSIS_H
|
|
#define SWIFT_SILOPTIMIZER_ANALYSIS_LOOPNESTANALYSIS_H
|
|
|
|
#include "swift/SILOptimizer/Analysis/Analysis.h"
|
|
#include "swift/Basic/BlotSetVector.h"
|
|
#include "swift/Basic/Range.h"
|
|
#include "swift/Basic/STLExtras.h"
|
|
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
|
|
#include "swift/SILOptimizer/Analysis/LoopAnalysis.h"
|
|
#include "swift/SILOptimizer/PassManager/PassManager.h"
|
|
#include "swift/SIL/LoopInfo.h"
|
|
#include "swift/SIL/SILBasicBlock.h"
|
|
#include "swift/SIL/SILFunction.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
namespace swift {
|
|
|
|
/// A loop region is a data structure which represents one of a basic block,
|
|
/// loop, or function.
|
|
///
|
|
/// In the case of a loop, function, it contains an internal
|
|
/// data structure that represents the subregions of the loop/function. This
|
|
/// data is tail allocated so that the basic block case is not penalized by
|
|
/// storing this unnecessary information.
|
|
class LoopRegion {
|
|
// FIXME: This should use llvm::TrailingObjects for its tail allocations, but
|
|
// that requires restructuring the file a bit.
|
|
|
|
/// This is a data structure that is an unsigned integer with a top bit flag
|
|
/// that says whether it is an RPO ID for a BB Region or is an RPO ID of the
|
|
/// header BB of a Loop Region that used as a placeholder to ensure that the
|
|
/// Loop is visited in the proper RPO order. When the placeholder flag is hit,
|
|
/// the subloop array should be searched for the pair that has that flag as a
|
|
/// first element. The loop's flag will be the second element of the pair.
|
|
struct SubregionID {
|
|
static constexpr unsigned IDBitSize = (sizeof(unsigned) * CHAR_BIT) - 1;
|
|
static constexpr unsigned MaxID = (unsigned(1) << IDBitSize) - 1;
|
|
unsigned IsLoop : 1;
|
|
unsigned ID : IDBitSize;
|
|
|
|
SubregionID(unsigned id, bool isloop) {
|
|
IsLoop = unsigned(isloop);
|
|
ID = id;
|
|
}
|
|
bool operator<(const SubregionID &Other) const { return ID < Other.ID; }
|
|
};
|
|
/// These checks are just for performance.
|
|
static_assert(IsTriviallyCopyable<SubregionID>::value,
|
|
"Expected trivially copyable type");
|
|
|
|
struct SubregionData;
|
|
friend class LoopRegionFunctionInfo;
|
|
|
|
public:
|
|
/// We operate in terms of FunctionTy, LoopTy, and BlockTy so that this can
|
|
/// potentially be ported to LLVM in the future.
|
|
using FunctionTy = SILFunction;
|
|
using LoopTy = SILLoop;
|
|
using BlockTy = SILBasicBlock;
|
|
|
|
/// A tagged data structure that stores in its bottom bit whether or not the
|
|
/// successor edge is non-local (implying it goes through the parent region).
|
|
struct SuccessorID {
|
|
static constexpr unsigned NumTaggedBits = 2;
|
|
static constexpr unsigned IDBitSize =
|
|
(sizeof(unsigned) * CHAR_BIT) - NumTaggedBits;
|
|
static constexpr unsigned MaxID = (unsigned(1) << IDBitSize) - 1;
|
|
|
|
private:
|
|
unsigned IsDead : 1;
|
|
|
|
public:
|
|
unsigned IsNonLocal : 1;
|
|
unsigned ID : IDBitSize;
|
|
|
|
SuccessorID(unsigned rawValue)
|
|
: IsDead(rawValue & 1), IsNonLocal((rawValue & 2) >> 1),
|
|
ID(rawValue >> 2) {}
|
|
SuccessorID(unsigned id, bool isnonlocal)
|
|
: IsDead(unsigned(false)), IsNonLocal(unsigned(isnonlocal)), ID(id) {}
|
|
|
|
bool operator<(const SuccessorID &Other) const { return ID < Other.ID; }
|
|
bool operator==(const SuccessorID &Other) const {
|
|
return ID == Other.ID && IsNonLocal == Other.IsNonLocal &&
|
|
IsDead == Other.IsDead;
|
|
}
|
|
bool operator!=(const SuccessorID &Other) const {
|
|
return !(*this == Other);
|
|
}
|
|
|
|
unsigned asInt() const { return *reinterpret_cast<const unsigned *>(this); }
|
|
|
|
struct ToLiveSucc {
|
|
Optional<SuccessorID> operator()(Optional<SuccessorID> ID) const {
|
|
return ID;
|
|
}
|
|
};
|
|
|
|
struct ToLiveLocalSucc {
|
|
Optional<unsigned> operator()(Optional<SuccessorID> ID) const {
|
|
if (!ID)
|
|
return None;
|
|
if ((*ID).IsNonLocal)
|
|
return None;
|
|
return (*ID).ID;
|
|
}
|
|
};
|
|
|
|
struct ToLiveNonLocalSucc {
|
|
Optional<unsigned> operator()(Optional<SuccessorID> ID) const {
|
|
if (!ID)
|
|
return None;
|
|
if (!(*ID).IsNonLocal)
|
|
return None;
|
|
return (*ID).ID;
|
|
}
|
|
};
|
|
};
|
|
// These checks are just for performance.
|
|
static_assert(IsTriviallyCopyable<SuccessorID>::value,
|
|
"Expected trivially copyable type");
|
|
|
|
/// An iterator that knows how to iterate over the subregion indices of a
|
|
/// region.
|
|
class subregion_iterator :
|
|
public std::iterator<std::bidirectional_iterator_tag, unsigned> {
|
|
friend struct SubregionData;
|
|
llvm::SmallVectorImpl<SubregionID>::const_iterator InnerIter;
|
|
const llvm::SmallVectorImpl<std::pair<unsigned, unsigned>> *Subloops;
|
|
|
|
/// A flag that says that this iterator is an invalid iterator belonging to
|
|
/// a basic block. The iterator can only be compared against another invalid
|
|
/// iterator. Any other use causes an unreachable being hit.
|
|
///
|
|
/// The reason this is needed is because basic blocks cannot have
|
|
/// subregions, yet many graph algorithms want to be able to iterate over
|
|
/// the subregions of a region regardless of whether it is a basic block,
|
|
/// loop, or function. By allowing for invalid iterators for basic blocks,
|
|
/// we can allow for this.
|
|
unsigned IsInvalid : 1;
|
|
|
|
subregion_iterator(
|
|
llvm::SmallVectorImpl<SubregionID>::const_iterator iter,
|
|
const llvm::SmallVectorImpl<std::pair<unsigned, unsigned>> *subloops)
|
|
: InnerIter(iter), Subloops(subloops), IsInvalid(false) {}
|
|
|
|
public:
|
|
using value_type = unsigned;
|
|
using reference = unsigned;
|
|
using pointer = void;
|
|
using iterator_category = std::bidirectional_iterator_tag;
|
|
using difference_type = int;
|
|
|
|
/// Construct a subregion_iterator suitable for use with a basic block. It
|
|
/// does not contain any data and can only be compared against another
|
|
/// invalid iterator (for which it will return true). Any other usage
|
|
/// results in an unreachable being hit.
|
|
subregion_iterator() : InnerIter(), Subloops(), IsInvalid(true) {}
|
|
|
|
/// Return the index of the current subregion index.
|
|
unsigned operator*() const {
|
|
if (IsInvalid)
|
|
llvm_unreachable("Invalid Iterator?!");
|
|
auto ID = *InnerIter;
|
|
if (!ID.IsLoop)
|
|
return ID.ID;
|
|
for (auto &p : *Subloops) {
|
|
if (p.first == ID.ID) {
|
|
return p.second;
|
|
}
|
|
}
|
|
llvm_unreachable("Out of sync subloops array?!");
|
|
}
|
|
|
|
subregion_iterator &operator++() {
|
|
if (IsInvalid)
|
|
llvm_unreachable("Invalid Iterator?!");
|
|
InnerIter++;
|
|
return *this;
|
|
}
|
|
subregion_iterator operator++(int) {
|
|
if (IsInvalid)
|
|
llvm_unreachable("Invalid Iterator?!");
|
|
return subregion_iterator(InnerIter++, Subloops);
|
|
}
|
|
subregion_iterator &operator--() {
|
|
if (IsInvalid)
|
|
llvm_unreachable("Invalid Iterator?!");
|
|
InnerIter--;
|
|
return *this;
|
|
}
|
|
subregion_iterator operator--(int) {
|
|
if (IsInvalid)
|
|
llvm_unreachable("Invalid Iterator?!");
|
|
return subregion_iterator(InnerIter--, Subloops);
|
|
}
|
|
bool operator==(subregion_iterator rhs) const {
|
|
if (IsInvalid) {
|
|
if (rhs.IsInvalid)
|
|
return true;
|
|
llvm_unreachable("Invalid Iterator?!");
|
|
}
|
|
return InnerIter == rhs.InnerIter;
|
|
}
|
|
bool operator!=(subregion_iterator rhs) const { return !(*this == rhs); }
|
|
};
|
|
using subregion_reverse_iterator = std::reverse_iterator<subregion_iterator>;
|
|
|
|
/// An iterator that knows how to iterate over the backedge indices of a
|
|
/// region.
|
|
class backedge_iterator
|
|
: public std::iterator<std::bidirectional_iterator_tag, unsigned> {
|
|
friend struct SubregionData;
|
|
using InnerIterTy = llvm::SmallVectorImpl<unsigned>::const_iterator;
|
|
llvm::Optional<InnerIterTy> InnerIter;
|
|
|
|
backedge_iterator(llvm::SmallVectorImpl<unsigned>::const_iterator iter)
|
|
: InnerIter(iter) {}
|
|
|
|
public:
|
|
using value_type = unsigned;
|
|
using reference = unsigned;
|
|
using pointer = void;
|
|
using iterator_category = std::bidirectional_iterator_tag;
|
|
using difference_type = int;
|
|
|
|
/// Construct a backedge_iterator suitable for use with a basic block. It
|
|
/// does not contain any data and can only be compared against another
|
|
/// invalid iterator (for which it will return true). Any other usage
|
|
/// results in an unreachable being hit.
|
|
backedge_iterator() : InnerIter() {}
|
|
|
|
bool hasValue() const { return InnerIter.hasValue(); }
|
|
|
|
/// Return the index of the current backedge index.
|
|
unsigned operator*() const { return **InnerIter; }
|
|
|
|
backedge_iterator &operator++() {
|
|
++(*InnerIter);
|
|
return *this;
|
|
}
|
|
backedge_iterator operator++(int) {
|
|
backedge_iterator iter = *this;
|
|
++iter;
|
|
return iter;
|
|
}
|
|
backedge_iterator &operator--() {
|
|
--(*InnerIter);
|
|
return *this;
|
|
}
|
|
backedge_iterator operator--(int) {
|
|
backedge_iterator iter = *this;
|
|
--iter;
|
|
return iter;
|
|
}
|
|
bool operator==(backedge_iterator rhs) const {
|
|
if (InnerIter.hasValue() != rhs.InnerIter.hasValue())
|
|
llvm_unreachable("Comparing uncomparable iterators");
|
|
// Now we know that the two either both have values or both do not have
|
|
// values.
|
|
if (!InnerIter.hasValue())
|
|
return true;
|
|
return *InnerIter == *rhs.InnerIter;
|
|
}
|
|
bool operator!=(backedge_iterator rhs) const { return !(*this == rhs); }
|
|
};
|
|
using backedge_reverse_iterator = std::reverse_iterator<backedge_iterator>;
|
|
|
|
private:
|
|
/// A pointer to one of a Loop, Basic Block, or Function represented by this
|
|
/// region.
|
|
llvm::PointerUnion3<FunctionTy *, LoopTy *, BlockTy *> Ptr;
|
|
|
|
/// The ID of this region.
|
|
unsigned ID;
|
|
|
|
/// The parent region of this ID if it exists.
|
|
llvm::Optional<unsigned> ParentID;
|
|
|
|
/// The IDs of the predecessor regions of this region.
|
|
llvm::SmallVector<unsigned, 4> Preds;
|
|
|
|
/// The IDs of the local and non-local successor regions of this region.
|
|
///
|
|
/// Let R1 be this region. A local successor of R1 is a region R2 for which
|
|
/// the inner most parent region that contains R2 is the same as the inner
|
|
/// most parent region that contains R1. A local successor is represented by
|
|
/// its real ID. A non-local successor is represented by the index of the
|
|
/// non-local successor in this region's parent's successor list.
|
|
///
|
|
/// Discussion: The reason that we have this separate representation is so
|
|
/// that if the non-local exit is to a block in a grand+-ancestor region of
|
|
/// this BB, we can represent each jump as individual non-local edges in
|
|
/// between loops.
|
|
///
|
|
/// *NOTE* This list is not sorted, but cannot have any duplicate
|
|
/// elements. We have a check in LoopRegionFunctionInfo::verify to make sure
|
|
/// that this stays true. The reason why this is necessary is that subregions
|
|
/// of a loop, may have a non-local successor edge pointed at this region's
|
|
/// successor edge. If we were to sort these edges, we would need to update
|
|
/// those subregion edges as well which is strictly not necessary.
|
|
SmallBlotSetVector<SuccessorID, 8> Succs;
|
|
|
|
/// True if this region the head of an edge that results from control flow
|
|
/// that we do not handle.
|
|
///
|
|
/// Currently this includes irreducible control flow and loops with multiple
|
|
/// backedges. We rely on loop canonicalization to handle the multiple
|
|
/// backedge case.
|
|
bool IsUnknownControlFlowEdgeHead;
|
|
|
|
/// True if this region the tail of an edge that results from control flow
|
|
/// that we do not handle.
|
|
///
|
|
/// Currently this includes irreducible control flow and loops with multiple
|
|
/// backedges. We rely on loop canonicalization to handle the multiple
|
|
/// backedge case.
|
|
bool IsUnknownControlFlowEdgeTail;
|
|
|
|
/// A tail allocated LoopSubregionData structure that is used to store state
|
|
/// about subregions of a loop.
|
|
///
|
|
/// We tail allocate this onto LoopRegions for loops/functions so that in the
|
|
/// case of having a Block, we do not have any memory size overhead of these
|
|
/// SmallVectors, but at the same time have reasonable memory performance. In
|
|
/// the case of loops this overhead is acceptable since we shouldn't have many
|
|
/// loops (compared to Blocks).
|
|
struct SubregionData {
|
|
/// The RPO number of the header block of this loop or function. This is
|
|
/// used as the RPO number for the whole region.
|
|
unsigned RPONumOfHeaderBlock;
|
|
|
|
/// The RPO number of the back edge blocks of this loop. We use a
|
|
/// SmallVector of size 1 since after loop canonicalization we will always
|
|
/// have exactly one back edge block. But we want to be correct even in a
|
|
/// non-canonicalized case implying that we need to be able to support
|
|
/// multiple backedge blocks.
|
|
llvm::SmallVector<unsigned, 1> BackedgeRegions;
|
|
|
|
/// A list of subregion IDs of this region sorted in RPO order.
|
|
///
|
|
/// This takes advantage of the fact that the ID of a basic block is the
|
|
/// block's RPO number.
|
|
///
|
|
/// This contains IDs that represent both basic blocks and loops that are
|
|
/// subregions of this region. What is key to notice is that a loop is
|
|
/// represented by the RPO number of its header. We use an auxiliary map to
|
|
/// map the preheader's RPO number to the loop's ID.
|
|
llvm::SmallVector<SubregionID, 16> Subregions;
|
|
|
|
/// A map from RPO number of a subregion loop's preheader to a subloop
|
|
/// regions id. This is necessary since we represent a loop in the
|
|
/// Subregions array by the RPO number of its header.
|
|
llvm::SmallVector<std::pair<unsigned, unsigned>, 2> Subloops;
|
|
|
|
/// A list of subregions with non-local successors. This is the actual ID
|
|
/// of the subregion since we do not care about any ordering.
|
|
llvm::SmallVector<unsigned, 2> ExitingSubregions;
|
|
|
|
subregion_iterator begin() const {
|
|
return subregion_iterator(Subregions.begin(), &Subloops);
|
|
}
|
|
subregion_iterator end() const {
|
|
return subregion_iterator(Subregions.end(), &Subloops);
|
|
}
|
|
subregion_reverse_iterator rbegin() const {
|
|
return subregion_reverse_iterator(begin());
|
|
}
|
|
subregion_reverse_iterator rend() const {
|
|
return subregion_reverse_iterator(end());
|
|
}
|
|
|
|
unsigned size() const { return Subregions.size(); }
|
|
bool empty() const { return Subregions.empty(); }
|
|
|
|
void addBlockSubregion(LoopRegion *R) {
|
|
assert(R->ID <= SubregionID::MaxID && "Unrepresentable ID");
|
|
Subregions.push_back(SubregionID(R->ID, false));
|
|
}
|
|
|
|
void addLoopSubregion(LoopRegion *L, LoopRegion *Header) {
|
|
assert(Header->ID <= SubregionID::MaxID && "Unrepresentable ID");
|
|
Subregions.push_back(SubregionID(Header->ID, true));
|
|
Subloops.push_back({Header->ID, L->ID});
|
|
}
|
|
|
|
void addBackedgeSubregion(unsigned ID) {
|
|
if (count(BackedgeRegions, ID))
|
|
return;
|
|
BackedgeRegions.push_back(ID);
|
|
}
|
|
|
|
backedge_iterator backedge_begin() const {
|
|
return backedge_iterator(BackedgeRegions.begin());
|
|
}
|
|
backedge_iterator backedge_end() const {
|
|
return backedge_iterator(BackedgeRegions.end());
|
|
}
|
|
backedge_reverse_iterator backedge_rbegin() const {
|
|
return backedge_reverse_iterator(backedge_begin());
|
|
}
|
|
backedge_reverse_iterator backedge_rend() const {
|
|
return backedge_reverse_iterator(backedge_end());
|
|
}
|
|
bool backedge_empty() const { return BackedgeRegions.empty(); }
|
|
unsigned backedge_size() const { return BackedgeRegions.size(); }
|
|
|
|
ArrayRef<unsigned> getBackedgeRegions() const { return BackedgeRegions; }
|
|
|
|
/// Once we finish processing a loop, we sort its subregions so that they
|
|
/// are guaranteed to be in RPO order. This works because each BB's ID is
|
|
/// its RPO number and we represent loops by the RPO number of their
|
|
/// preheader (with a flag in the first bit to say to look in the subloop
|
|
/// array for the *real* ID of the loop).
|
|
///
|
|
/// TODO: Is this necessary? We visit BBs in RPO order. This means that we
|
|
/// should always add BBs in RPO order to subregion lists, no? For now I am
|
|
/// going to sort just to be careful while bringing this up.
|
|
void sortSubregions() { std::sort(Subregions.begin(), Subregions.end()); }
|
|
};
|
|
public:
|
|
~LoopRegion();
|
|
|
|
/// These will assert if this is not a loop region. If this is a loop region,
|
|
/// the forward iterators will give the IDs of the subregions in RPO order.
|
|
/// The backward iterators will give the IDs of the subregions in PO order.
|
|
///
|
|
/// The reason why all of this work is being done for subregions is because we
|
|
/// are working around the following contradiction:
|
|
///
|
|
/// 1. There are not that many loops in a function so it makes sense to take
|
|
/// advantage of this to store more data that makes performing analysis easy
|
|
/// (such as the IDs of subregions of the loop in RPO order).
|
|
/// 2. There are a lot of BBs in a function so it makes sense to try to
|
|
/// minimize the amount of state stored for each BB.
|
|
/// 3. This is a data structure that attempts to generalize over both of them
|
|
/// without using dynamic dispatch.
|
|
///
|
|
/// We use tail allocation of the extra data for loops so we do not incur the
|
|
/// memory cost of large SmallVectors for BBs.
|
|
subregion_iterator subregion_begin() const {
|
|
if (isBlock())
|
|
return subregion_iterator();
|
|
return getSubregionData().begin();
|
|
}
|
|
|
|
/// This is the end equivalent of subregion_begin(). Please see comment on
|
|
/// subregion_begin().
|
|
subregion_iterator subregion_end() const {
|
|
if (isBlock())
|
|
return subregion_iterator();
|
|
return getSubregionData().end();
|
|
}
|
|
|
|
bool subregions_empty() const {
|
|
if (isBlock())
|
|
return true;
|
|
return getSubregionData().empty();
|
|
}
|
|
|
|
unsigned subregions_size() const {
|
|
if (isBlock())
|
|
return 0;
|
|
return getSubregionData().size();
|
|
}
|
|
|
|
subregion_reverse_iterator subregion_rbegin() const {
|
|
if (isBlock())
|
|
return subregion_reverse_iterator();
|
|
return getSubregionData().rbegin();
|
|
}
|
|
|
|
subregion_reverse_iterator subregion_rend() const {
|
|
if (isBlock())
|
|
return subregion_reverse_iterator();
|
|
return getSubregionData().rend();
|
|
}
|
|
|
|
llvm::iterator_range<subregion_iterator> getSubregions() const {
|
|
return {subregion_begin(), subregion_end()};
|
|
}
|
|
|
|
llvm::iterator_range<subregion_reverse_iterator>
|
|
getReverseSubregions() const {
|
|
return {subregion_rbegin(), subregion_rend()};
|
|
}
|
|
|
|
/// Returns true if \p R is an immediate subregion of this region.
|
|
bool containsSubregion(LoopRegion *R) {
|
|
auto End = subregion_end();
|
|
return std::find(subregion_begin(), End, R->getID()) != End;
|
|
}
|
|
|
|
/// Returns an ArrayRef containing IDs of the exiting subregions of this
|
|
/// region. The exit regions associated with the exiting subregions are the
|
|
/// end points of the non-local edges. This asserts if this is a region
|
|
/// representing a block.
|
|
ArrayRef<unsigned> getExitingSubregions() const {
|
|
return getSubregionData().ExitingSubregions;
|
|
}
|
|
|
|
bool isBackedgeRegion(unsigned ID) const {
|
|
if (isBlock())
|
|
return false;
|
|
return count(getSubregionData().getBackedgeRegions(), ID);
|
|
}
|
|
|
|
ArrayRef<unsigned> getBackedgeRegions() const {
|
|
if (isBlock())
|
|
return ArrayRef<unsigned>();
|
|
return getSubregionData().getBackedgeRegions();
|
|
}
|
|
|
|
Optional<unsigned> getBackedgeRegion() const {
|
|
if (isBlock())
|
|
return None;
|
|
auto bedge_begin = getSubregionData().backedge_begin();
|
|
auto bedge_end = getSubregionData().backedge_end();
|
|
if (bedge_begin == bedge_end)
|
|
return None;
|
|
return *bedge_begin;
|
|
}
|
|
|
|
using backedge_iterator = backedge_iterator;
|
|
backedge_iterator backedge_begin() const {
|
|
if (isBlock())
|
|
return backedge_iterator();
|
|
return getSubregionData().backedge_begin();
|
|
}
|
|
backedge_iterator backedge_end() const {
|
|
if (isBlock())
|
|
return backedge_iterator();
|
|
return getSubregionData().backedge_end();
|
|
}
|
|
bool backedge_empty() const {
|
|
if (isBlock())
|
|
return true;
|
|
return getSubregionData().backedge_empty();
|
|
}
|
|
unsigned backedge_size() const {
|
|
if (isBlock())
|
|
return 0;
|
|
return getSubregionData().backedge_size();
|
|
}
|
|
|
|
using pred_const_iterator = decltype(Preds)::const_iterator;
|
|
pred_const_iterator pred_begin() const { return Preds.begin(); }
|
|
pred_const_iterator pred_end() const { return Preds.end(); }
|
|
bool pred_empty() const { return Preds.empty(); }
|
|
unsigned pred_size() const { return Preds.size(); }
|
|
iterator_range<pred_const_iterator> getPreds() const {
|
|
return {pred_begin(), pred_end()};
|
|
}
|
|
|
|
using const_succ_iterator = decltype(Succs)::const_iterator;
|
|
const_succ_iterator succ_begin() const { return Succs.begin(); }
|
|
const_succ_iterator succ_end() const { return Succs.end(); }
|
|
bool succ_empty() const { return Succs.empty(); }
|
|
unsigned succ_size() const { return Succs.size(); }
|
|
|
|
private:
|
|
using InnerSuccRange = IteratorRange<decltype(Succs)::const_iterator>;
|
|
|
|
public:
|
|
using SuccRange =
|
|
OptionalTransformRange<InnerSuccRange, SuccessorID::ToLiveSucc>;
|
|
using LocalSuccRange =
|
|
OptionalTransformRange<InnerSuccRange, SuccessorID::ToLiveLocalSucc>;
|
|
using NonLocalSuccRange =
|
|
OptionalTransformRange<InnerSuccRange, SuccessorID::ToLiveNonLocalSucc>;
|
|
SuccRange getSuccs() const;
|
|
LocalSuccRange getLocalSuccs() const;
|
|
NonLocalSuccRange getNonLocalSuccs() const;
|
|
|
|
BlockTy *getBlock() const;
|
|
LoopTy *getLoop() const;
|
|
FunctionTy *getFunction() const;
|
|
|
|
bool isBlock() const { return Ptr.is<BlockTy *>(); }
|
|
bool isLoop() const { return Ptr.is<LoopTy *>(); }
|
|
bool isFunction() const { return Ptr.is<FunctionTy *>(); }
|
|
|
|
/// Is this the head of an edge that causes unknown control flow.
|
|
///
|
|
/// This means that dataflow that enters the region must not propagate any
|
|
/// information into this region from predecessors.
|
|
bool isUnknownControlFlowEdgeHead() const {
|
|
return IsUnknownControlFlowEdgeHead;
|
|
}
|
|
|
|
/// Is this the head of an edge that causes unknown control flow.
|
|
///
|
|
/// This means that dataflow state must not be propagated out of this region.
|
|
bool isUnknownControlFlowEdgeTail() const {
|
|
return IsUnknownControlFlowEdgeTail;
|
|
}
|
|
|
|
/// Returns the ID of this region in the region array.
|
|
///
|
|
/// For basic blocks this is the RPO number of the basic block. This is done
|
|
/// just as a convenient way to compress our region data structures.
|
|
unsigned getID() const { return ID; }
|
|
|
|
/// Return the ID of the parent region of this BB. Asserts if this is a
|
|
/// function region.
|
|
Optional<unsigned> getParentID() const { return ParentID; }
|
|
|
|
unsigned getRPONumber() const {
|
|
if (isBlock())
|
|
return getID();
|
|
return getSubregionData().RPONumOfHeaderBlock;
|
|
}
|
|
|
|
void dump() const;
|
|
void print(llvm::raw_ostream &os, bool insertSpaces = false) const;
|
|
void dumpName() const;
|
|
void printName(llvm::raw_ostream &os) const;
|
|
|
|
private:
|
|
LoopRegion(LoopTy *L, unsigned Idx)
|
|
: Ptr(L), ID(Idx), ParentID(), Preds(), Succs(),
|
|
IsUnknownControlFlowEdgeHead(false),
|
|
IsUnknownControlFlowEdgeTail(false) {}
|
|
|
|
LoopRegion(BlockTy *BB, unsigned Idx)
|
|
: Ptr(BB), ID(Idx), ParentID(), Preds(), Succs(),
|
|
IsUnknownControlFlowEdgeHead(false),
|
|
IsUnknownControlFlowEdgeTail(false) {}
|
|
|
|
LoopRegion(FunctionTy *F, unsigned Idx)
|
|
: Ptr(F), ID(Idx), ParentID(), Preds(), Succs(),
|
|
IsUnknownControlFlowEdgeHead(false),
|
|
IsUnknownControlFlowEdgeTail(false) {}
|
|
|
|
void setParent(LoopRegion *PR) {
|
|
assert(!isFunction() && "Functions cannot be subregions");
|
|
assert(!PR->isBlock() && "BB regions cannot be parents of a region");
|
|
ParentID = PR->getID();
|
|
}
|
|
|
|
void addPred(LoopRegion *LNR) {
|
|
assert(!isFunction() && "Functions cannot have predecessors");
|
|
if (count(getPreds(), LNR->getID()))
|
|
return;
|
|
Preds.push_back(LNR->ID);
|
|
}
|
|
|
|
unsigned addSucc(LoopRegion *Successor) {
|
|
assert(!isFunction() && "Functions cannot have successors");
|
|
return Succs.insert(SuccessorID(Successor->getID(), false));
|
|
}
|
|
|
|
void replacePred(unsigned OldPredID, unsigned NewPredID) {
|
|
// Check if we already have NewPred in our list. If so, we just delete
|
|
// OldPredID.
|
|
if (count(Preds, NewPredID)) {
|
|
// If this becomes a performance issue due to copying/moving/etc (which it
|
|
// most likely will not), just use the marked dead model like successor
|
|
// does.
|
|
auto Iter = std::remove(Preds.begin(), Preds.end(), OldPredID);
|
|
Preds.erase(Iter);
|
|
return;
|
|
}
|
|
std::replace(Preds.begin(), Preds.end(), OldPredID, NewPredID);
|
|
}
|
|
|
|
/// Replace OldSuccID by NewSuccID, just deleting OldSuccID if what NewSuccID
|
|
/// is already in the list.
|
|
void replaceSucc(SuccessorID OldSucc, SuccessorID NewSucc);
|
|
|
|
/// Set the IsDead flag on all successors of this region that have an id \p
|
|
/// ID.
|
|
///
|
|
/// Due to the creation of up-edges during loop region construction, we can
|
|
/// never actually remove successors. Instead we mark successors as being dead
|
|
/// and ignore such values when iterating.
|
|
void removeLocalSucc(unsigned ID) { Succs.erase(SuccessorID(ID, false)); }
|
|
|
|
void addBlockSubregion(LoopRegion *R) {
|
|
assert(!isBlock() && "Blocks cannot have subregions");
|
|
assert(R->isBlock() && "Assumed R was a basic block");
|
|
R->setParent(this);
|
|
getSubregionData().addBlockSubregion(R);
|
|
}
|
|
|
|
void addLoopSubregion(LoopRegion *L, LoopRegion *Header) {
|
|
assert(!isBlock() && "Blocks cannot have subregions");
|
|
assert(L->isLoop() && "Assumed L was a loop");
|
|
assert(Header->isBlock() && "Assumed Header was a loop");
|
|
L->setParent(this);
|
|
getSubregionData().addLoopSubregion(L, Header);
|
|
}
|
|
|
|
|
|
SubregionData &getSubregionData() {
|
|
assert(!isBlock() && "BBs do not have subregion data");
|
|
return reinterpret_cast<SubregionData &>(*(this + 1));
|
|
}
|
|
|
|
const SubregionData &getSubregionData() const {
|
|
assert(!isBlock() && "BBs do not have subregion data");
|
|
return reinterpret_cast<const SubregionData &>(*(this + 1));
|
|
}
|
|
};
|
|
|
|
} // end swift namespace
|
|
|
|
namespace llvm {
|
|
|
|
raw_ostream &operator<<(raw_ostream &os, swift::LoopRegion &LR);
|
|
raw_ostream &operator<<(raw_ostream &os, swift::LoopRegion::SuccessorID &S);
|
|
|
|
template <> struct DenseMapInfo<swift::LoopRegion::SuccessorID> {
|
|
using Type = swift::LoopRegion::SuccessorID;
|
|
|
|
static_assert(sizeof(Type) == sizeof(unsigned),
|
|
"Expected SuccessorID to be the size of an unsigned!");
|
|
static inline Type getEmptyKey() {
|
|
return Type(DenseMapInfo<unsigned>::getEmptyKey());
|
|
}
|
|
static inline Type getTombstoneKey() {
|
|
return Type(DenseMapInfo<unsigned>::getTombstoneKey());
|
|
}
|
|
static unsigned getHashValue(const swift::LoopRegion::SuccessorID Val) {
|
|
return DenseMapInfo<unsigned>::getHashValue(Val.asInt());
|
|
}
|
|
static bool isEqual(const swift::LoopRegion::SuccessorID LHS,
|
|
const swift::LoopRegion::SuccessorID RHS) {
|
|
return LHS == RHS;
|
|
}
|
|
};
|
|
|
|
} // end llvm namespace
|
|
|
|
namespace swift {
|
|
|
|
class LoopRegionFunctionInfo {
|
|
using RegionTy = LoopRegion;
|
|
using BlockTy = SILBasicBlock;
|
|
using LoopInfoTy = SILLoopInfo;
|
|
using LoopTy = SILLoop;
|
|
using FunctionTy = SILFunction;
|
|
|
|
/// The function that this data structure contains loop regions for.
|
|
FunctionTy *F;
|
|
|
|
/// A bump ptr allocator that we allocate regions from.
|
|
llvm::BumpPtrAllocator Allocator;
|
|
|
|
/// The ID in the IDToRegionMap of the Region associated with \p F.
|
|
llvm::Optional<unsigned> FunctionRegionID;
|
|
|
|
/// A map from a BB to the ID in the IDToRegionMap of the Region associated
|
|
/// with the BB.
|
|
///
|
|
/// *NOTE* This ID is *also* the function level RPO number of the BB.
|
|
llvm::DenseMap<BlockTy *, unsigned> BBToIDMap;
|
|
|
|
/// A map from a Loop to the ID in the IDToRegionMap of the Region associated
|
|
/// with the loop.
|
|
llvm::DenseMap<LoopTy *, unsigned> LoopToIDMap;
|
|
|
|
/// A map from an unsigned integer ID to a region.
|
|
///
|
|
/// *WARNING* Before modifying the initialization of this field of the data
|
|
/// structure please read the comment below:
|
|
///
|
|
/// We assign IDs to BBs, Loops, and the top level Function, so that we can
|
|
/// abstract above the underlying type of any specific region. A key thing to
|
|
/// notice is that we always allocate regions associated with BBs before
|
|
/// regions associated with Loops or the top level function (1). The reason
|
|
/// that we do this is to ensure that each BBs ID is the RPO number of the BB
|
|
/// at the function level. This is done so we do not have to represent the RPO
|
|
/// numbers in a separate array. This means that when initializing this data
|
|
/// structure, we need to be careful about how we initialize regions in order
|
|
/// to preserve said property.
|
|
///
|
|
/// In order to avoid having to initialize loop/function regions after BB
|
|
/// regions (which would cause us to have to visit BBs twice), we instead
|
|
/// allocate the memory for all of the BBs up front and set the pointer entry
|
|
/// to be nullptr.
|
|
///
|
|
/// Then when we initialize BBRegions, we just assign the resulting Region's
|
|
/// pointer into the array using its RPO index rather than performing a
|
|
/// push_back. For regions associated with Loops, Functions we still perform a
|
|
/// push_back. This enables us to initialize regions for all types at the same
|
|
/// time while preserving said property.
|
|
///
|
|
/// (1) Currently there is no work done to ensure that the creation of the
|
|
/// function level region will have any ordering with respect to the creation
|
|
/// of any of the loop regions.
|
|
std::vector<RegionTy *> IDToRegionMap;
|
|
|
|
/// True if all BB regions have been created. Only in Debug Builds. Used to
|
|
/// fire asserts to make sure that:
|
|
///
|
|
/// RegionTy *createRegion(SILBasicBlock *, unsigned)
|
|
///
|
|
/// is only called in initializeBBRegions and everywhere else:
|
|
///
|
|
/// RegionTy *getRegion(SILBasicBlock *)
|
|
///
|
|
/// is called.
|
|
#ifndef NDEBUG
|
|
unsigned AllBBRegionsCreated : 1;
|
|
#endif
|
|
|
|
public:
|
|
LoopRegionFunctionInfo(FunctionTy *F, PostOrderFunctionInfo *POI,
|
|
LoopInfoTy *LI);
|
|
~LoopRegionFunctionInfo();
|
|
|
|
RegionTy *getRegion(unsigned RegionID) const {
|
|
return IDToRegionMap[RegionID];
|
|
}
|
|
|
|
RegionTy *getRegionForNonLocalSuccessor(const RegionTy *Child,
|
|
unsigned SuccID) const;
|
|
|
|
Optional<unsigned> getGrandparentID(const RegionTy *GrandChild) {
|
|
if (auto ParentID = GrandChild->getParentID()) {
|
|
return getRegion(*ParentID)->getParentID();
|
|
}
|
|
return None;
|
|
}
|
|
|
|
/// Look up the region associated with this block and return it. Asserts if
|
|
/// the block does not have a region associated with it.
|
|
///
|
|
/// Regions associated with blocks are only created by the function
|
|
/// createRegion(). FunctionTy and LoopTy have their RegionTys created via
|
|
/// getRegion() lazily. For more information read the documentation for
|
|
/// IDToRegionMap.
|
|
RegionTy *getRegion(BlockTy *BB) const;
|
|
|
|
/// Return the RegionTy associated with \p F. Creates the region if it does
|
|
/// not exist yet.
|
|
RegionTy *getRegion(FunctionTy *F) const;
|
|
|
|
/// Return the RegionTy associated with \p Loop. Creates the region if it does
|
|
/// not exist yet.
|
|
RegionTy *getRegion(LoopTy *Loop) const;
|
|
|
|
using iterator = decltype(IDToRegionMap)::iterator;
|
|
using const_iterator = decltype(IDToRegionMap)::const_iterator;
|
|
iterator begin() { return IDToRegionMap.begin(); }
|
|
iterator end() { return IDToRegionMap.end(); }
|
|
const_iterator begin() const { return IDToRegionMap.begin(); }
|
|
const_iterator end() const { return IDToRegionMap.end(); }
|
|
unsigned size() const { return IDToRegionMap.size(); }
|
|
bool empty() const { return IDToRegionMap.empty(); }
|
|
llvm::iterator_range<const_iterator> getRegions() const {
|
|
return {begin(), end()};
|
|
}
|
|
|
|
RegionTy *getTopLevelRegion() const { return getRegion(F); }
|
|
FunctionTy *getFunction() const { return F; }
|
|
|
|
void dump() const;
|
|
void print(llvm::raw_ostream &os) const;
|
|
void viewLoopRegions() const;
|
|
void verify();
|
|
|
|
private:
|
|
RegionTy *createRegion(BlockTy *BB, unsigned RPONum);
|
|
|
|
/// Initialize regions for all basic blocks.
|
|
///
|
|
/// This initializes all BBs to have their regular predecessors, successors in
|
|
/// the CFG ignoring BBs that are unreachable from the entry. During
|
|
/// initialization of loops, we modify these edges in the region graph by
|
|
/// removing and or substituting edges to loops for basic block edges. See
|
|
/// large comment at the top of the file.
|
|
void initializeBlockRegions(PostOrderFunctionInfo *PI, LoopInfoTy *LI);
|
|
|
|
/// Initialize regions for all loops.
|
|
///
|
|
/// This traverses the loop nest bottom up:
|
|
///
|
|
/// 1. Removing backedges of loops.
|
|
/// 2. Changing header predecessors to be loop predecessors.
|
|
/// 3. Changing exiting block successors to be loop successors.
|
|
///
|
|
/// For more information see large comment at the top of LoopRegionAnalysis.h.
|
|
void initializeLoopRegions(LoopInfoTy *LI);
|
|
|
|
/// Initialize the subregions of a function, treating the function as a top
|
|
/// level loop.
|
|
void
|
|
initializeFunctionRegion(iterator_range<LoopInfoTy::iterator> TopLevelLoops);
|
|
|
|
/// This is a refactored routine that handles common initialization of loops
|
|
/// and functions.
|
|
void
|
|
initializeLoopFunctionRegion(RegionTy *ParentRegion,
|
|
iterator_range<LoopInfoTy::iterator> SubLoops);
|
|
|
|
/// This helper routine rewrites all predecessors of the header of \p Loop to
|
|
/// be predecessors of \p Loop and vis-a-versa. Returns the region for the
|
|
/// SubLoopHeaderRegion
|
|
RegionTy *
|
|
rewriteLoopHeaderPredecessors(LoopTy *Loop, RegionTy *LoopRegion);
|
|
|
|
/// This helper routine rewrites all successors of loop region exiting blocks
|
|
/// to be successors of the loop. It also removes backedges and ignores
|
|
/// non-backedge edges in the loop.
|
|
void
|
|
rewriteLoopExitingBlockSuccessors(LoopTy *Loop, RegionTy *LoopRegion);
|
|
|
|
/// This helper routine for a given block, creates successor edges in between
|
|
/// the block's region and all of the regions associated with the block's
|
|
/// successor blocks.
|
|
void initializeBlockRegionSuccessors(BlockTy *BB, RegionTy *BBRegion,
|
|
PostOrderFunctionInfo *PI);
|
|
|
|
/// This helper method goes through the predecessors of the basic block \p
|
|
/// NonHeaderBB and determines if any of them are back edges. Since we know
|
|
/// that \p NonHeaderBB is a basic block that has not been identified by loop
|
|
/// info as a loop header, we know that these backedges are not known to us
|
|
/// via loop info. We treat them as irreducible control flow edges to be
|
|
/// conservative. In truth some of them may not be due to deficiencies in loop
|
|
/// info.
|
|
///
|
|
/// TODO: This needs a better name.
|
|
void
|
|
markIrreducibleLoopPredecessorsOfNonLoopHeader(BlockTy *NonHeaderBB,
|
|
RegionTy *NonHeaderBBRegion,
|
|
PostOrderFunctionInfo *PI);
|
|
|
|
/// This helper method takes in a loop that has multiple loop latches and
|
|
/// marks each of those loop latches as being unknown control flow tails. We
|
|
/// do not support loops with multiple loop latches and instead rely on loops
|
|
/// to be canonicalized to have one back edge. But we still need to be
|
|
/// conservatively correct.
|
|
///
|
|
/// TODO: This needs a better name.
|
|
void
|
|
markMultipleLoopLatchLoopBackEdges(RegionTy *LoopHeaderRegion,
|
|
LoopTy *L,
|
|
PostOrderFunctionInfo *PI);
|
|
|
|
/// Recursively visit all the descendants of Parent. If there is a non-local
|
|
/// successor edge path that points to a dead edge in Parent, mark the
|
|
/// descendant non-local successor edge as dead.
|
|
void propagateLivenessDownNonLocalSuccessorEdges(LoopRegion *Parent);
|
|
};
|
|
|
|
class LoopRegionAnalysis : public FunctionAnalysisBase<LoopRegionFunctionInfo> {
|
|
SILLoopAnalysis *SLA;
|
|
PostOrderAnalysis *POA;
|
|
|
|
public:
|
|
LoopRegionAnalysis(SILModule *M)
|
|
: FunctionAnalysisBase<LoopRegionFunctionInfo>(AnalysisKind::LoopRegion) {}
|
|
|
|
LoopRegionAnalysis(const LoopRegionAnalysis &) = delete;
|
|
LoopRegionAnalysis &operator=(const LoopRegionAnalysis &) = delete;
|
|
|
|
virtual ~LoopRegionAnalysis() {}
|
|
|
|
virtual void initialize(SILPassManager *PM) override;
|
|
|
|
static bool classof(const SILAnalysis *S) {
|
|
return S->getKind() == AnalysisKind::LoopRegion;
|
|
}
|
|
|
|
virtual LoopRegionFunctionInfo *newFunctionAnalysis(SILFunction *F) override {
|
|
return new LoopRegionFunctionInfo(F, POA->get(F), SLA->get(F));
|
|
}
|
|
|
|
virtual bool shouldInvalidate(SILAnalysis::InvalidationKind K) override {
|
|
return K & InvalidationKind::Branches;
|
|
}
|
|
};
|
|
|
|
} // end namespace swift
|
|
|
|
#endif
|