//===--- LoopRegionAnalysis.h -----------------------------------*- C++ -*-===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://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::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(this); } struct ToLiveSucc { std::optional operator()(std::optional ID) const { return ID; } }; struct ToLiveLocalSucc { std::optional operator()(std::optional ID) const { if (!ID) return std::nullopt; if ((*ID).IsNonLocal) return std::nullopt; return static_cast((*ID).ID); } }; struct ToLiveNonLocalSucc { std::optional operator()(std::optional ID) const { if (!ID) return std::nullopt; if (!(*ID).IsNonLocal) return std::nullopt; return static_cast((*ID).ID); } }; }; // These checks are just for performance. static_assert(IsTriviallyCopyable::value, "Expected trivially copyable type"); /// An iterator that knows how to iterate over the subregion indices of a /// region. class subregion_iterator { friend struct SubregionData; llvm::SmallVectorImpl::const_iterator InnerIter; const llvm::SmallVectorImpl> *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::const_iterator iter, const llvm::SmallVectorImpl> *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; /// An iterator that knows how to iterate over the backedge indices of a /// region. class backedge_iterator { friend struct SubregionData; using InnerIterTy = llvm::SmallVectorImpl::const_iterator; std::optional InnerIter; backedge_iterator(llvm::SmallVectorImpl::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.has_value(); } /// 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.has_value() != rhs.InnerIter.has_value()) llvm_unreachable("Comparing uncomparable iterators"); // Now we know that the two either both have values or both do not have // values. if (!InnerIter.has_value()) return true; return *InnerIter == *rhs.InnerIter; } bool operator!=(backedge_iterator rhs) const { return !(*this == rhs); } }; using backedge_reverse_iterator = std::reverse_iterator; private: /// A pointer to one of a Loop, Basic Block, or Function represented by this /// region. llvm::PointerUnion Ptr; /// The ID of this region. unsigned ID; /// The parent region of this ID if it exists. std::optional ParentID; /// The IDs of the predecessor regions of this region. llvm::SmallVector 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 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 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 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, 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 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(end()); } subregion_reverse_iterator rend() const { return subregion_reverse_iterator(begin()); } 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 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(); } iterator_range getSubregions() const { return {subregion_begin(), subregion_end()}; } iterator_range 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 getExitingSubregions() const { return getSubregionData().ExitingSubregions; } bool isBackedgeRegion(unsigned ID) const { if (isBlock()) return false; return count(getSubregionData().getBackedgeRegions(), ID); } ArrayRef getBackedgeRegions() const { if (isBlock()) return ArrayRef(); return getSubregionData().getBackedgeRegions(); } std::optional getBackedgeRegion() const { if (isBlock()) return std::nullopt; auto bedge_begin = getSubregionData().backedge_begin(); auto bedge_end = getSubregionData().backedge_end(); if (bedge_begin == bedge_end) return std::nullopt; 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 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 = iterator_range; public: using SuccRange = OptionalTransformRange; using LocalSuccRange = OptionalTransformRange; using NonLocalSuccRange = OptionalTransformRange; SuccRange getSuccs() const; LocalSuccRange getLocalSuccs() const; NonLocalSuccRange getNonLocalSuccs() const; BlockTy *getBlock() const; LoopTy *getLoop() const; FunctionTy *getFunction() const; bool isBlock() const { return Ptr.is(); } bool isLoop() const { return Ptr.is(); } bool isFunction() const { return Ptr.is(); } /// 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. std::optional getParentID() const { return ParentID; } unsigned getRPONumber() const { if (isBlock()) return getID(); return getSubregionData().RPONumOfHeaderBlock; } void dump(bool isVerbose = false) const; void print(llvm::raw_ostream &os, bool isShort = false, bool isVerbose = 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)).first; } 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(*(this + 1)); } const SubregionData &getSubregionData() const { assert(!isBlock() && "BBs do not have subregion data"); return reinterpret_cast(*(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 { 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::getEmptyKey()); } static inline Type getTombstoneKey() { return Type(DenseMapInfo::getTombstoneKey()); } static unsigned getHashValue(const swift::LoopRegion::SuccessorID Val) { return DenseMapInfo::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. std::optional 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 BBToIDMap; /// A map from a Loop to the ID in the IDToRegionMap of the Region associated /// with the loop. llvm::DenseMap 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 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; std::optional getGrandparentID(const RegionTy *GrandChild) { if (auto ParentID = GrandChild->getParentID()) { return getRegion(*ParentID)->getParentID(); } return std::nullopt; } /// 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(); } iterator_range 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 TopLevelLoops); /// This is a refactored routine that handles common initialization of loops /// and functions. void initializeLoopFunctionRegion(RegionTy *ParentRegion, iterator_range 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 { SILLoopAnalysis *SLA; PostOrderAnalysis *POA; public: LoopRegionAnalysis(SILModule *M) : FunctionAnalysisBase( SILAnalysisKind::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() == SILAnalysisKind::LoopRegion; } virtual std::unique_ptr newFunctionAnalysis(SILFunction *F) override { return std::make_unique(F, POA->get(F), SLA->get(F)); } virtual bool shouldInvalidate(SILAnalysis::InvalidationKind K) override { return K & InvalidationKind::Branches; } }; } // end namespace swift #endif