| //===- BottomUpIPAnalysis.h - Bottom-up IP analysis base-class --*- 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file contains a base class for bottom-up interprocedural analysis. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef SWIFT_SILOPTIMIZER_ANALYSIS_BOTTOMUPIPANALYSIS_H |
| #define SWIFT_SILOPTIMIZER_ANALYSIS_BOTTOMUPIPANALYSIS_H |
| |
| #include "swift/SILOptimizer/Analysis/Analysis.h" |
| #include "swift/SIL/SILInstruction.h" |
| #include "swift/SIL/ApplySite.h" |
| #include "llvm/ADT/SmallVector.h" |
| |
| namespace swift { |
| |
| /// An abstract base class for interprocedural analysis which are computed in |
| /// bottom-up order of the call-graph. |
| /// It provides utilities for automatic invalidation and updating the analysis. |
| class BottomUpIPAnalysis : public SILAnalysis { |
| |
| /// Each update cycle gets a unique ID. |
| /// It is incremented for each recomputation of the analysis. |
| /// It is mainly used to check for invalid CallerEntries in |
| /// FunctionInfoBase::Callers (for details see there). |
| int CurrentUpdateID = 0; |
| |
| protected: |
| |
| template<typename FunctionInfo> class FunctionInfoWorkList; |
| |
| /// An abstract base class for a \p FunctionInfo class, which stores the |
| /// analysis information for a function. |
| /// This base class stores the administrative information needed for |
| /// invalidation and updating the analysis. |
| /// In the following "this function" refers to the derivative function. |
| template<typename FunctionInfo> class FunctionInfoBase { |
| public: |
| |
| /// An entry in the caller list. |
| struct CallerEntry { |
| /// The caller function. |
| FunctionInfo *Caller; |
| |
| /// The function apply-site. |
| /// It can be an apply instruction or an instruction, |
| /// which results in a call, e.g. strong_release results in |
| /// a destructor call. |
| SILInstruction *FAS; |
| |
| /// Returns false, if the caller was invalidated since this entry had been |
| /// created. |
| bool isValid() const { return Caller->UpdateID == UpdateID; } |
| |
| private: |
| /// Used to check if the caller and its apply-site is still valid. |
| int UpdateID; |
| |
| CallerEntry(FunctionInfo *Caller, SILInstruction *FAS, int UpdateID) : |
| Caller(Caller), FAS(FAS), UpdateID(UpdateID) { } |
| |
| friend class FunctionInfoBase<FunctionInfo>; |
| }; |
| |
| private: |
| |
| /// The list of callers which must be invalidated if this function gets |
| /// invalidated. Note that the list may contain invalid entries for already |
| /// invalidated callers. Those entries are removed lazily in |
| /// removeInvalidCallers(). |
| /// The lazy removal of invalid entries avoids that we additionally need to |
| /// store a callee-set. Also it reduces the number of times we need to |
| /// iterate through the Callers (in case multiple caller functions are |
| /// invalidated at the same time). |
| /// Note that Callers can't be a pure hashed set-like container because the |
| /// iteration order must be deterministic. |
| llvm::SmallVector<CallerEntry, 8> Callers; |
| |
| /// In which update-cycle the analysis was computed for this function. |
| /// A 0-value means that the analysis for this function was not computed |
| /// yet or got invalidated. |
| int UpdateID = 0; |
| |
| /// Special states for StateAndPosition. |
| enum { |
| NotVisited = -2, |
| Visited = -1, |
| FirstPosition = 0 |
| }; |
| |
| /// The position in the function schedule, or if not scheduled yet, any of |
| /// the special states. |
| /// This field is only used during recomputation. |
| int StateAndPosition = NotVisited; |
| |
| /// How many callees are not in the function schedule yet. |
| /// This field is only used during recomputation. |
| int numUnscheduledCallees = 0; |
| |
| |
| /// Removes invalid caller entries. |
| void removeInvalidCallers() { |
| Callers.erase(std::remove_if(Callers.begin(), Callers.end(), |
| [](const CallerEntry &E) { |
| return !E.isValid(); |
| }), Callers.end()); |
| } |
| |
| friend class FunctionInfoWorkList<FunctionInfo>; |
| friend class BottomUpIPAnalysis; |
| |
| public: |
| |
| /// Returns true if the analysis data for this function is computed and |
| /// up-to-date. |
| bool isValid() const { return UpdateID != 0; } |
| |
| /// Returns true if this function was already visited during recomputation. |
| bool isVisited() const { return StateAndPosition >= Visited; } |
| |
| /// Returns true if this function is already scheduled in the bottom-up |
| /// order during recomputation. |
| bool isScheduled() const { return StateAndPosition >= FirstPosition; } |
| |
| /// Returns true if this function is scheduled after \p RHS in the bottom-up |
| /// order, i.e. is considered as a "caller" of \p RHS. |
| bool isScheduledAfter(const FunctionInfo *RHS) const { |
| assert(isScheduled() && RHS->isScheduled() && |
| "operating with unscheduled functions?"); |
| return StateAndPosition > RHS->StateAndPosition; |
| } |
| |
| /// Returns the list of caller-entries. |
| ArrayRef<CallerEntry> getCallers() const { return Callers; } |
| |
| /// Adds a caller for this function. |
| void addCaller(FunctionInfo *CallerInfo, SILInstruction *FAS) { |
| Callers.push_back(CallerEntry(CallerInfo, FAS, CallerInfo->UpdateID)); |
| if (!isScheduled()) |
| ++CallerInfo->numUnscheduledCallees; |
| } |
| |
| void addCaller(FunctionInfo *CallerInfo, FullApplySite FAS) { |
| addCaller(CallerInfo, FAS.getInstruction()); |
| } |
| }; |
| |
| /// Computes and stores a bottom-up function order. |
| template<typename FunctionInfo> class BottomUpFunctionOrder { |
| |
| typedef llvm::SmallVector<FunctionInfo *, 8> FunctionInfoList; |
| |
| /// The final bottom-up function order. |
| FunctionInfoList Scheduled; |
| |
| /// Functions which are not initially scheduled in the bottom-up order. |
| FunctionInfoList InitiallyUnscheduled; |
| |
| /// The number of visited functions. |
| unsigned numVisited = 0; |
| |
| /// The BottomUpIPAnalysis::CurrentUpdateID. |
| int CurrentUpdateID; |
| |
| public: |
| |
| /// The constructor. The \p CurrentUpdateID is the |
| /// BottomUpIPAnalysis::CurrentUpdateID. |
| BottomUpFunctionOrder(int CurrentUpdateID) : |
| CurrentUpdateID(CurrentUpdateID) { |
| assert(CurrentUpdateID > 0 && "did not allocate an UpdateID"); |
| } |
| |
| ~BottomUpFunctionOrder() { |
| assert(InitiallyUnscheduled.empty() && |
| "not finished scheduling"); |
| assert(Scheduled.size() == numVisited && |
| "missed some functions to schedule"); |
| for (FunctionInfo *FInfo : Scheduled) { |
| assert(FInfo->isScheduled() && |
| "function not scheduled at the end of recomputation"); |
| assert(FInfo->numUnscheduledCallees == 0 && |
| "something basic is broken in scheduling"); |
| assert(FInfo->isValid() && |
| "function not recomputed at the end of recomputation"); |
| FInfo->StateAndPosition = FunctionInfoBase<FunctionInfo>::NotVisited; |
| } |
| } |
| |
| /// Returns the begin of the bottom-up function order. |
| typename FunctionInfoList::const_iterator begin() const { |
| return Scheduled.begin(); |
| } |
| |
| /// Returns the end of the bottom-up function order. |
| typename FunctionInfoList::const_iterator end() const { |
| return Scheduled.end(); |
| } |
| |
| /// Returns true if the analysis for \p FInfo was recomputed during the |
| /// current update-cycle. |
| bool wasRecomputedWithCurrentUpdateID(FunctionInfo *FInfo) { |
| return FInfo->UpdateID == CurrentUpdateID; |
| } |
| |
| /// Should be called when first visiting \p FInfo during recomputation. |
| /// Returns true if the analysis is still valid for \p FInfo and doesn't |
| /// need to be recomputed. |
| bool prepareForVisiting(FunctionInfo *FInfo) { |
| assert(!FInfo->isVisited() && |
| "function visited multiple times"); |
| assert(FInfo->numUnscheduledCallees == 0 && |
| "already added callees at the begin of visiting a function"); |
| numVisited++; |
| FInfo->StateAndPosition = FunctionInfoBase<FunctionInfo>::Visited; |
| if (FInfo->isValid()) { |
| // Now it's good time to remove invalid caller entries. |
| // Note: we don't have to do this for function which are recomputed. |
| FInfo->removeInvalidCallers(); |
| return true; |
| } |
| InitiallyUnscheduled.push_back(FInfo); |
| // Set to valid. |
| FInfo->UpdateID = CurrentUpdateID; |
| return false; |
| } |
| |
| /// Tries to schedule \p FInfo onto the bottom-up function order. |
| /// This succeeds if \p FInfo does not have any unscheduled callees. |
| /// Should be called after visiting \p FInfo during recomputation. |
| void tryToSchedule(FunctionInfo *FInfo) { |
| assert(FInfo->isVisited() && |
| "tried to schedule function which was not visited"); |
| assert(!FInfo->isScheduled() && |
| "function scheduled multiple times"); |
| if (FInfo->numUnscheduledCallees == 0) { |
| FInfo->StateAndPosition = (int)Scheduled.size(); |
| Scheduled.push_back(FInfo); |
| return; |
| } |
| assert(wasRecomputedWithCurrentUpdateID(FInfo) && |
| "not recomputed function cannot have unscheduled callees"); |
| } |
| |
| /// Finishes the bottom-up function schedule. |
| void finishScheduling() { |
| unsigned Idx = 0; |
| bool NeedAnotherIteration = false; |
| do { |
| /// Schedule all functions we can. |
| while (Idx < Scheduled.size()) { |
| FunctionInfo *FInfo = Scheduled[Idx++]; |
| for (const auto &E : FInfo->Callers) { |
| if (E.Caller->isVisited() && !E.Caller->isScheduled()) { |
| E.Caller->numUnscheduledCallees--; |
| tryToSchedule(E.Caller); |
| } |
| } |
| } |
| NeedAnotherIteration = false; |
| // Check if there are still unscheduled functions, which means that |
| // there is a cycle in the call graph. |
| while (!InitiallyUnscheduled.empty()) { |
| FunctionInfo *FInfo = InitiallyUnscheduled.pop_back_val(); |
| if (!FInfo->isScheduled()) { |
| // Break the cycle by scheduling the first unscheduled node we |
| // found. |
| FInfo->numUnscheduledCallees = 0; |
| tryToSchedule(FInfo); |
| assert(FInfo->isScheduled() && |
| "really, we should be able to schedule this function"); |
| NeedAnotherIteration = true; |
| break; |
| } |
| } |
| } while (NeedAnotherIteration); |
| } |
| }; |
| |
| BottomUpIPAnalysis(SILAnalysisKind k) : SILAnalysis(k) {} |
| |
| /// Increments the CurrentUpdateID. |
| /// Should be called at the beginning of a recomputation. |
| void allocNewUpdateID() { ++CurrentUpdateID; } |
| |
| /// Returns the ID of the current update-cycle. |
| int getCurrentUpdateID() const { return CurrentUpdateID; } |
| |
| /// Invalidates \p FInfo, including all analysis data which depend on it, i.e. |
| /// the callers. |
| template<typename FunctionInfo> |
| void invalidateIncludingAllCallers(FunctionInfo *FInfo) { |
| llvm::SmallVector<FunctionInfo *, 8> WorkList; |
| WorkList.push_back(FInfo); |
| |
| while (!WorkList.empty()) { |
| FunctionInfo *FInfo = WorkList.pop_back_val(); |
| for (const auto &E : FInfo->Callers) { |
| if (E.isValid() && E.Caller->isValid()) |
| WorkList.push_back(E.Caller); |
| } |
| FInfo->clear(); |
| FInfo->Callers.clear(); |
| FInfo->UpdateID = 0; |
| } |
| } |
| }; |
| |
| } // end namespace swift |
| |
| #endif // SWIFT_SILOPTIMIZER_ANALYSIS_BOTTOMUPIPANALYSIS_H |