| // Copyright (c) 2018 Google LLC. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef SOURCE_OPT_LOOP_DEPENDENCE_H_ |
| #define SOURCE_OPT_LOOP_DEPENDENCE_H_ |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <list> |
| #include <map> |
| #include <memory> |
| #include <ostream> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "source/opt/instruction.h" |
| #include "source/opt/ir_context.h" |
| #include "source/opt/loop_descriptor.h" |
| #include "source/opt/scalar_analysis.h" |
| |
| namespace spvtools { |
| namespace opt { |
| |
| // Stores information about dependence between a load and a store wrt a single |
| // loop in a loop nest. |
| // DependenceInformation |
| // * UNKNOWN if no dependence information can be gathered or is gathered |
| // for it. |
| // * DIRECTION if a dependence direction could be found, but not a |
| // distance. |
| // * DISTANCE if a dependence distance could be found. |
| // * PEEL if peeling either the first or last iteration will break |
| // dependence between the given load and store. |
| // * IRRELEVANT if it has no effect on the dependence between the given |
| // load and store. |
| // |
| // If peel_first == true, the analysis has found that peeling the first |
| // iteration of this loop will break dependence. |
| // |
| // If peel_last == true, the analysis has found that peeling the last iteration |
| // of this loop will break dependence. |
| class DistanceEntry { |
| public: |
| enum DependenceInformation { |
| UNKNOWN = 0, |
| DIRECTION = 1, |
| DISTANCE = 2, |
| PEEL = 3, |
| IRRELEVANT = 4, |
| POINT = 5 |
| }; |
| enum Directions { |
| NONE = 0, |
| LT = 1, |
| EQ = 2, |
| LE = 3, |
| GT = 4, |
| NE = 5, |
| GE = 6, |
| ALL = 7 |
| }; |
| DependenceInformation dependence_information; |
| Directions direction; |
| int64_t distance; |
| bool peel_first; |
| bool peel_last; |
| int64_t point_x; |
| int64_t point_y; |
| |
| DistanceEntry() |
| : dependence_information(DependenceInformation::UNKNOWN), |
| direction(Directions::ALL), |
| distance(0), |
| peel_first(false), |
| peel_last(false), |
| point_x(0), |
| point_y(0) {} |
| |
| explicit DistanceEntry(Directions direction_) |
| : dependence_information(DependenceInformation::DIRECTION), |
| direction(direction_), |
| distance(0), |
| peel_first(false), |
| peel_last(false), |
| point_x(0), |
| point_y(0) {} |
| |
| DistanceEntry(Directions direction_, int64_t distance_) |
| : dependence_information(DependenceInformation::DISTANCE), |
| direction(direction_), |
| distance(distance_), |
| peel_first(false), |
| peel_last(false), |
| point_x(0), |
| point_y(0) {} |
| |
| DistanceEntry(int64_t x, int64_t y) |
| : dependence_information(DependenceInformation::POINT), |
| direction(Directions::ALL), |
| distance(0), |
| peel_first(false), |
| peel_last(false), |
| point_x(x), |
| point_y(y) {} |
| |
| bool operator==(const DistanceEntry& rhs) const { |
| return direction == rhs.direction && peel_first == rhs.peel_first && |
| peel_last == rhs.peel_last && distance == rhs.distance && |
| point_x == rhs.point_x && point_y == rhs.point_y; |
| } |
| |
| bool operator!=(const DistanceEntry& rhs) const { return !(*this == rhs); } |
| }; |
| |
| // Stores a vector of DistanceEntrys, one per loop in the analysis. |
| // A DistanceVector holds all of the information gathered in a dependence |
| // analysis wrt the loops stored in the LoopDependenceAnalysis performing the |
| // analysis. |
| class DistanceVector { |
| public: |
| explicit DistanceVector(size_t size) : entries(size, DistanceEntry{}) {} |
| |
| explicit DistanceVector(std::vector<DistanceEntry> entries_) |
| : entries(entries_) {} |
| |
| DistanceEntry& GetEntry(size_t index) { return entries[index]; } |
| const DistanceEntry& GetEntry(size_t index) const { return entries[index]; } |
| |
| std::vector<DistanceEntry>& GetEntries() { return entries; } |
| const std::vector<DistanceEntry>& GetEntries() const { return entries; } |
| |
| bool operator==(const DistanceVector& rhs) const { |
| if (entries.size() != rhs.entries.size()) { |
| return false; |
| } |
| for (size_t i = 0; i < entries.size(); ++i) { |
| if (entries[i] != rhs.entries[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| bool operator!=(const DistanceVector& rhs) const { return !(*this == rhs); } |
| |
| private: |
| std::vector<DistanceEntry> entries; |
| }; |
| |
| class DependenceLine; |
| class DependenceDistance; |
| class DependencePoint; |
| class DependenceNone; |
| class DependenceEmpty; |
| |
| class Constraint { |
| public: |
| explicit Constraint(const Loop* loop) : loop_(loop) {} |
| enum ConstraintType { Line, Distance, Point, None, Empty }; |
| |
| virtual ConstraintType GetType() const = 0; |
| |
| virtual ~Constraint() {} |
| |
| // Get the loop this constraint belongs to. |
| const Loop* GetLoop() const { return loop_; } |
| |
| bool operator==(const Constraint& other) const; |
| |
| bool operator!=(const Constraint& other) const; |
| |
| #define DeclareCastMethod(target) \ |
| virtual target* As##target() { return nullptr; } \ |
| virtual const target* As##target() const { return nullptr; } |
| DeclareCastMethod(DependenceLine); |
| DeclareCastMethod(DependenceDistance); |
| DeclareCastMethod(DependencePoint); |
| DeclareCastMethod(DependenceNone); |
| DeclareCastMethod(DependenceEmpty); |
| #undef DeclareCastMethod |
| |
| protected: |
| const Loop* loop_; |
| }; |
| |
| class DependenceLine : public Constraint { |
| public: |
| DependenceLine(SENode* a, SENode* b, SENode* c, const Loop* loop) |
| : Constraint(loop), a_(a), b_(b), c_(c) {} |
| |
| ConstraintType GetType() const final { return Line; } |
| |
| DependenceLine* AsDependenceLine() final { return this; } |
| const DependenceLine* AsDependenceLine() const final { return this; } |
| |
| SENode* GetA() const { return a_; } |
| SENode* GetB() const { return b_; } |
| SENode* GetC() const { return c_; } |
| |
| private: |
| SENode* a_; |
| SENode* b_; |
| SENode* c_; |
| }; |
| |
| class DependenceDistance : public Constraint { |
| public: |
| DependenceDistance(SENode* distance, const Loop* loop) |
| : Constraint(loop), distance_(distance) {} |
| |
| ConstraintType GetType() const final { return Distance; } |
| |
| DependenceDistance* AsDependenceDistance() final { return this; } |
| const DependenceDistance* AsDependenceDistance() const final { return this; } |
| |
| SENode* GetDistance() const { return distance_; } |
| |
| private: |
| SENode* distance_; |
| }; |
| |
| class DependencePoint : public Constraint { |
| public: |
| DependencePoint(SENode* source, SENode* destination, const Loop* loop) |
| : Constraint(loop), source_(source), destination_(destination) {} |
| |
| ConstraintType GetType() const final { return Point; } |
| |
| DependencePoint* AsDependencePoint() final { return this; } |
| const DependencePoint* AsDependencePoint() const final { return this; } |
| |
| SENode* GetSource() const { return source_; } |
| SENode* GetDestination() const { return destination_; } |
| |
| private: |
| SENode* source_; |
| SENode* destination_; |
| }; |
| |
| class DependenceNone : public Constraint { |
| public: |
| DependenceNone() : Constraint(nullptr) {} |
| ConstraintType GetType() const final { return None; } |
| |
| DependenceNone* AsDependenceNone() final { return this; } |
| const DependenceNone* AsDependenceNone() const final { return this; } |
| }; |
| |
| class DependenceEmpty : public Constraint { |
| public: |
| DependenceEmpty() : Constraint(nullptr) {} |
| ConstraintType GetType() const final { return Empty; } |
| |
| DependenceEmpty* AsDependenceEmpty() final { return this; } |
| const DependenceEmpty* AsDependenceEmpty() const final { return this; } |
| }; |
| |
| // Provides dependence information between a store instruction and a load |
| // instruction inside the same loop in a loop nest. |
| // |
| // The analysis can only check dependence between stores and loads with regard |
| // to the loop nest it is created with. |
| // |
| // The analysis can output debugging information to a stream. The output |
| // describes the control flow of the analysis and what information it can deduce |
| // at each step. |
| // SetDebugStream and ClearDebugStream are provided for this functionality. |
| // |
| // The dependency algorithm is based on the 1990 Paper |
| // Practical Dependence Testing |
| // Gina Goff, Ken Kennedy, Chau-Wen Tseng |
| // |
| // The algorithm first identifies subscript pairs between the load and store. |
| // Each pair is tested until all have been tested or independence is found. |
| // The number of induction variables in a pair determines which test to perform |
| // on it; |
| // Zero Index Variable (ZIV) is used when no induction variables are present |
| // in the pair. |
| // Single Index Variable (SIV) is used when only one induction variable is |
| // present, but may occur multiple times in the pair. |
| // Multiple Index Variable (MIV) is used when more than one induction variable |
| // is present in the pair. |
| class LoopDependenceAnalysis { |
| public: |
| LoopDependenceAnalysis(IRContext* context, std::vector<const Loop*> loops) |
| : context_(context), |
| loops_(loops), |
| scalar_evolution_(context), |
| debug_stream_(nullptr), |
| constraints_{} {} |
| |
| // Finds the dependence between |source| and |destination|. |
| // |source| should be an OpLoad. |
| // |destination| should be an OpStore. |
| // Any direction and distance information found will be stored in |
| // |distance_vector|. |
| // Returns true if independence is found, false otherwise. |
| bool GetDependence(const Instruction* source, const Instruction* destination, |
| DistanceVector* distance_vector); |
| |
| // Returns true if |subscript_pair| represents a Zero Index Variable pair |
| // (ZIV) |
| bool IsZIV(const std::pair<SENode*, SENode*>& subscript_pair); |
| |
| // Returns true if |subscript_pair| represents a Single Index Variable |
| // (SIV) pair |
| bool IsSIV(const std::pair<SENode*, SENode*>& subscript_pair); |
| |
| // Returns true if |subscript_pair| represents a Multiple Index Variable |
| // (MIV) pair |
| bool IsMIV(const std::pair<SENode*, SENode*>& subscript_pair); |
| |
| // Finds the lower bound of |loop| as an SENode* and returns the result. |
| // The lower bound is the starting value of the loops induction variable |
| SENode* GetLowerBound(const Loop* loop); |
| |
| // Finds the upper bound of |loop| as an SENode* and returns the result. |
| // The upper bound is the last value before the loop exit condition is met. |
| SENode* GetUpperBound(const Loop* loop); |
| |
| // Returns true if |value| is between |bound_one| and |bound_two| (inclusive). |
| bool IsWithinBounds(int64_t value, int64_t bound_one, int64_t bound_two); |
| |
| // Finds the bounds of |loop| as upper_bound - lower_bound and returns the |
| // resulting SENode. |
| // If the operations can not be completed a nullptr is returned. |
| SENode* GetTripCount(const Loop* loop); |
| |
| // Returns the SENode* produced by building an SENode from the result of |
| // calling GetInductionInitValue on |loop|. |
| // If the operation can not be completed a nullptr is returned. |
| SENode* GetFirstTripInductionNode(const Loop* loop); |
| |
| // Returns the SENode* produced by building an SENode from the result of |
| // GetFirstTripInductionNode + (GetTripCount - 1) * induction_coefficient. |
| // If the operation can not be completed a nullptr is returned. |
| SENode* GetFinalTripInductionNode(const Loop* loop, |
| SENode* induction_coefficient); |
| |
| // Returns all the distinct loops that appear in |nodes|. |
| std::set<const Loop*> CollectLoops( |
| const std::vector<SERecurrentNode*>& nodes); |
| |
| // Returns all the distinct loops that appear in |source| and |destination|. |
| std::set<const Loop*> CollectLoops(SENode* source, SENode* destination); |
| |
| // Returns true if |distance| is provably outside the loop bounds. |
| // |coefficient| must be an SENode representing the coefficient of the |
| // induction variable of |loop|. |
| // This method is able to handle some symbolic cases which IsWithinBounds |
| // can't handle. |
| bool IsProvablyOutsideOfLoopBounds(const Loop* loop, SENode* distance, |
| SENode* coefficient); |
| |
| // Sets the ostream for debug information for the analysis. |
| void SetDebugStream(std::ostream& debug_stream) { |
| debug_stream_ = &debug_stream; |
| } |
| |
| // Clears the stored ostream to stop debug information printing. |
| void ClearDebugStream() { debug_stream_ = nullptr; } |
| |
| // Returns the ScalarEvolutionAnalysis used by this analysis. |
| ScalarEvolutionAnalysis* GetScalarEvolution() { return &scalar_evolution_; } |
| |
| // Creates a new constraint of type |T| and returns the pointer to it. |
| template <typename T, typename... Args> |
| Constraint* make_constraint(Args&&... args) { |
| constraints_.push_back( |
| std::unique_ptr<Constraint>(new T(std::forward<Args>(args)...))); |
| |
| return constraints_.back().get(); |
| } |
| |
| // Subscript partitioning as described in Figure 1 of 'Practical Dependence |
| // Testing' by Gina Goff, Ken Kennedy, and Chau-Wen Tseng from PLDI '91. |
| // Partitions the subscripts into independent subscripts and minimally coupled |
| // sets of subscripts. |
| // Returns the partitioning of subscript pairs. Sets of size 1 indicates an |
| // independent subscript-pair and others indicate coupled sets. |
| using PartitionedSubscripts = |
| std::vector<std::set<std::pair<Instruction*, Instruction*>>>; |
| PartitionedSubscripts PartitionSubscripts( |
| const std::vector<Instruction*>& source_subscripts, |
| const std::vector<Instruction*>& destination_subscripts); |
| |
| // Returns the Loop* matching the loop for |subscript_pair|. |
| // |subscript_pair| must be an SIV pair. |
| const Loop* GetLoopForSubscriptPair( |
| const std::pair<SENode*, SENode*>& subscript_pair); |
| |
| // Returns the DistanceEntry matching the loop for |subscript_pair|. |
| // |subscript_pair| must be an SIV pair. |
| DistanceEntry* GetDistanceEntryForSubscriptPair( |
| const std::pair<SENode*, SENode*>& subscript_pair, |
| DistanceVector* distance_vector); |
| |
| // Returns the DistanceEntry matching |loop|. |
| DistanceEntry* GetDistanceEntryForLoop(const Loop* loop, |
| DistanceVector* distance_vector); |
| |
| // Returns a vector of Instruction* which form the subscripts of the array |
| // access defined by the access chain |instruction|. |
| std::vector<Instruction*> GetSubscripts(const Instruction* instruction); |
| |
| // Delta test as described in Figure 3 of 'Practical Dependence |
| // Testing' by Gina Goff, Ken Kennedy, and Chau-Wen Tseng from PLDI '91. |
| bool DeltaTest( |
| const std::vector<std::pair<SENode*, SENode*>>& coupled_subscripts, |
| DistanceVector* dv_entry); |
| |
| // Constraint propagation as described in Figure 5 of 'Practical Dependence |
| // Testing' by Gina Goff, Ken Kennedy, and Chau-Wen Tseng from PLDI '91. |
| std::pair<SENode*, SENode*> PropagateConstraints( |
| const std::pair<SENode*, SENode*>& subscript_pair, |
| const std::vector<Constraint*>& constraints); |
| |
| // Constraint intersection as described in Figure 4 of 'Practical Dependence |
| // Testing' by Gina Goff, Ken Kennedy, and Chau-Wen Tseng from PLDI '91. |
| Constraint* IntersectConstraints(Constraint* constraint_0, |
| Constraint* constraint_1, |
| const SENode* lower_bound, |
| const SENode* upper_bound); |
| |
| // Returns true if each loop in |loops| is in a form supported by this |
| // analysis. |
| // A loop is supported if it has a single induction variable and that |
| // induction variable has a step of +1 or -1 per loop iteration. |
| bool CheckSupportedLoops(std::vector<const Loop*> loops); |
| |
| // Returns true if |loop| is in a form supported by this analysis. |
| // A loop is supported if it has a single induction variable and that |
| // induction variable has a step of +1 or -1 per loop iteration. |
| bool IsSupportedLoop(const Loop* loop); |
| |
| private: |
| IRContext* context_; |
| |
| // The loop nest we are analysing the dependence of. |
| std::vector<const Loop*> loops_; |
| |
| // The ScalarEvolutionAnalysis used by this analysis to store and perform much |
| // of its logic. |
| ScalarEvolutionAnalysis scalar_evolution_; |
| |
| // The ostream debug information for the analysis to print to. |
| std::ostream* debug_stream_; |
| |
| // Stores all the constraints created by the analysis. |
| std::list<std::unique_ptr<Constraint>> constraints_; |
| |
| // Returns true if independence can be proven and false if it can't be proven. |
| bool ZIVTest(const std::pair<SENode*, SENode*>& subscript_pair); |
| |
| // Analyzes the subscript pair to find an applicable SIV test. |
| // Returns true if independence can be proven and false if it can't be proven. |
| bool SIVTest(const std::pair<SENode*, SENode*>& subscript_pair, |
| DistanceVector* distance_vector); |
| |
| // Takes the form a*i + c1, a*i + c2 |
| // When c1 and c2 are loop invariant and a is constant |
| // distance = (c1 - c2)/a |
| // < if distance > 0 |
| // direction = = if distance = 0 |
| // > if distance < 0 |
| // Returns true if independence is proven and false if it can't be proven. |
| bool StrongSIVTest(SENode* source, SENode* destination, SENode* coeff, |
| DistanceEntry* distance_entry); |
| |
| // Takes for form a*i + c1, a*i + c2 |
| // where c1 and c2 are loop invariant and a is constant. |
| // c1 and/or c2 contain one or more SEValueUnknown nodes. |
| bool SymbolicStrongSIVTest(SENode* source, SENode* destination, |
| SENode* coefficient, |
| DistanceEntry* distance_entry); |
| |
| // Takes the form a1*i + c1, a2*i + c2 |
| // where a1 = 0 |
| // distance = (c1 - c2) / a2 |
| // Returns true if independence is proven and false if it can't be proven. |
| bool WeakZeroSourceSIVTest(SENode* source, SERecurrentNode* destination, |
| SENode* coefficient, |
| DistanceEntry* distance_entry); |
| |
| // Takes the form a1*i + c1, a2*i + c2 |
| // where a2 = 0 |
| // distance = (c2 - c1) / a1 |
| // Returns true if independence is proven and false if it can't be proven. |
| bool WeakZeroDestinationSIVTest(SERecurrentNode* source, SENode* destination, |
| SENode* coefficient, |
| DistanceEntry* distance_entry); |
| |
| // Takes the form a1*i + c1, a2*i + c2 |
| // where a1 = -a2 |
| // distance = (c2 - c1) / 2*a1 |
| // Returns true if independence is proven and false if it can't be proven. |
| bool WeakCrossingSIVTest(SENode* source, SENode* destination, |
| SENode* coefficient, DistanceEntry* distance_entry); |
| |
| // Uses the def_use_mgr to get the instruction referenced by |
| // SingleWordInOperand(|id|) when called on |instruction|. |
| Instruction* GetOperandDefinition(const Instruction* instruction, int id); |
| |
| // Perform the GCD test if both, the source and the destination nodes, are in |
| // the form a0*i0 + a1*i1 + ... an*in + c. |
| bool GCDMIVTest(const std::pair<SENode*, SENode*>& subscript_pair); |
| |
| // Finds the number of induction variables in |node|. |
| // Returns -1 on failure. |
| int64_t CountInductionVariables(SENode* node); |
| |
| // Finds the number of induction variables shared between |source| and |
| // |destination|. |
| // Returns -1 on failure. |
| int64_t CountInductionVariables(SENode* source, SENode* destination); |
| |
| // Takes the offset from the induction variable and subtracts the lower bound |
| // from it to get the constant term added to the induction. |
| // Returns the resuting constant term, or nullptr if it could not be produced. |
| SENode* GetConstantTerm(const Loop* loop, SERecurrentNode* induction); |
| |
| // Marks all the distance entries in |distance_vector| that were relate to |
| // loops in |loops_| but were not used in any subscripts as irrelevant to the |
| // to the dependence test. |
| void MarkUnsusedDistanceEntriesAsIrrelevant(const Instruction* source, |
| const Instruction* destination, |
| DistanceVector* distance_vector); |
| |
| // Converts |value| to a std::string and returns the result. |
| // This is required because Android does not compile std::to_string. |
| template <typename valueT> |
| std::string ToString(valueT value) { |
| std::ostringstream string_stream; |
| string_stream << value; |
| return string_stream.str(); |
| } |
| |
| // Prints |debug_msg| and "\n" to the ostream pointed to by |debug_stream_|. |
| // Won't print anything if |debug_stream_| is nullptr. |
| void PrintDebug(std::string debug_msg); |
| }; |
| |
| } // namespace opt |
| } // namespace spvtools |
| |
| #endif // SOURCE_OPT_LOOP_DEPENDENCE_H_ |