| // 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_SCALAR_ANALYSIS_H_ | |
| #define SOURCE_OPT_SCALAR_ANALYSIS_H_ | |
| #include <algorithm> | |
| #include <cstdint> | |
| #include <map> | |
| #include <memory> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include "source/opt/basic_block.h" | |
| #include "source/opt/instruction.h" | |
| #include "source/opt/scalar_analysis_nodes.h" | |
| namespace spvtools { | |
| namespace opt { | |
| class IRContext; | |
| class Loop; | |
| // Manager for the Scalar Evolution analysis. Creates and maintains a DAG of | |
| // scalar operations generated from analysing the use def graph from incoming | |
| // instructions. Each node is hashed as it is added so like node (for instance, | |
| // two induction variables i=0,i++ and j=0,j++) become the same node. After | |
| // creating a DAG with AnalyzeInstruction it can the be simplified into a more | |
| // usable form with SimplifyExpression. | |
| class ScalarEvolutionAnalysis { | |
| public: | |
| explicit ScalarEvolutionAnalysis(IRContext* context); | |
| // Create a unary negative node on |operand|. | |
| SENode* CreateNegation(SENode* operand); | |
| // Creates a subtraction between the two operands by adding |operand_1| to the | |
| // negation of |operand_2|. | |
| SENode* CreateSubtraction(SENode* operand_1, SENode* operand_2); | |
| // Create an addition node between two operands. The |simplify| when set will | |
| // allow the function to return an SEConstant instead of an addition if the | |
| // two input operands are also constant. | |
| SENode* CreateAddNode(SENode* operand_1, SENode* operand_2); | |
| // Create a multiply node between two operands. | |
| SENode* CreateMultiplyNode(SENode* operand_1, SENode* operand_2); | |
| // Create a node representing a constant integer. | |
| SENode* CreateConstant(int64_t integer); | |
| // Create a value unknown node, such as a load. | |
| SENode* CreateValueUnknownNode(const Instruction* inst); | |
| // Create a CantComputeNode. Used to exit out of analysis. | |
| SENode* CreateCantComputeNode(); | |
| // Create a new recurrent node with |offset| and |coefficient|, with respect | |
| // to |loop|. | |
| SENode* CreateRecurrentExpression(const Loop* loop, SENode* offset, | |
| SENode* coefficient); | |
| // Construct the DAG by traversing use def chain of |inst|. | |
| SENode* AnalyzeInstruction(const Instruction* inst); | |
| // Simplify the |node| by grouping like terms or if contains a recurrent | |
| // expression, rewrite the graph so the whole DAG (from |node| down) is in | |
| // terms of that recurrent expression. | |
| // | |
| // For example. | |
| // Induction variable i=0, i++ would produce Rec(0,1) so i+1 could be | |
| // transformed into Rec(1,1). | |
| // | |
| // X+X*2+Y-Y+34-17 would be transformed into 3*X + 17, where X and Y are | |
| // ValueUnknown nodes (such as a load instruction). | |
| SENode* SimplifyExpression(SENode* node); | |
| // Add |prospective_node| into the cache and return a raw pointer to it. If | |
| // |prospective_node| is already in the cache just return the raw pointer. | |
| SENode* GetCachedOrAdd(std::unique_ptr<SENode> prospective_node); | |
| // Checks that the graph starting from |node| is invariant to the |loop|. | |
| bool IsLoopInvariant(const Loop* loop, const SENode* node) const; | |
| // Sets |is_gt_zero| to true if |node| represent a value always strictly | |
| // greater than 0. The result of |is_gt_zero| is valid only if the function | |
| // returns true. | |
| bool IsAlwaysGreaterThanZero(SENode* node, bool* is_gt_zero) const; | |
| // Sets |is_ge_zero| to true if |node| represent a value greater or equals to | |
| // 0. The result of |is_ge_zero| is valid only if the function returns true. | |
| bool IsAlwaysGreaterOrEqualToZero(SENode* node, bool* is_ge_zero) const; | |
| // Find the recurrent term belonging to |loop| in the graph starting from | |
| // |node| and return the coefficient of that recurrent term. Constant zero | |
| // will be returned if no recurrent could be found. |node| should be in | |
| // simplest form. | |
| SENode* GetCoefficientFromRecurrentTerm(SENode* node, const Loop* loop); | |
| // Return a rebuilt graph starting from |node| with the recurrent expression | |
| // belonging to |loop| being zeroed out. Returned node will be simplified. | |
| SENode* BuildGraphWithoutRecurrentTerm(SENode* node, const Loop* loop); | |
| // Return the recurrent term belonging to |loop| if it appears in the graph | |
| // starting at |node| or null if it doesn't. | |
| SERecurrentNode* GetRecurrentTerm(SENode* node, const Loop* loop); | |
| SENode* UpdateChildNode(SENode* parent, SENode* child, SENode* new_child); | |
| // The loops in |loop_pair| will be considered the same when constructing | |
| // SERecurrentNode objects. This enables analysing dependencies that will be | |
| // created during loop fusion. | |
| void AddLoopsToPretendAreTheSame( | |
| const std::pair<const Loop*, const Loop*>& loop_pair) { | |
| pretend_equal_[std::get<1>(loop_pair)] = std::get<0>(loop_pair); | |
| } | |
| private: | |
| SENode* AnalyzeConstant(const Instruction* inst); | |
| // Handles both addition and subtraction. If the |instruction| is OpISub | |
| // then the resulting node will be op1+(-op2) otherwise if it is OpIAdd then | |
| // the result will be op1+op2. |instruction| must be OpIAdd or OpISub. | |
| SENode* AnalyzeAddOp(const Instruction* instruction); | |
| SENode* AnalyzeMultiplyOp(const Instruction* multiply); | |
| SENode* AnalyzePhiInstruction(const Instruction* phi); | |
| IRContext* context_; | |
| // A map of instructions to SENodes. This is used to track recurrent | |
| // expressions as they are added when analyzing instructions. Recurrent | |
| // expressions come from phi nodes which by nature can include recursion so we | |
| // check if nodes have already been built when analyzing instructions. | |
| std::map<const Instruction*, SENode*> recurrent_node_map_; | |
| // On creation we create and cache the CantCompute node so we not need to | |
| // perform a needless create step. | |
| SENode* cached_cant_compute_; | |
| // Helper functor to allow two unique_ptr to nodes to be compare. Only | |
| // needed | |
| // for the unordered_set implementation. | |
| struct NodePointersEquality { | |
| bool operator()(const std::unique_ptr<SENode>& lhs, | |
| const std::unique_ptr<SENode>& rhs) const { | |
| return *lhs == *rhs; | |
| } | |
| }; | |
| // Cache of nodes. All pointers to the nodes are references to the memory | |
| // managed by they set. | |
| std::unordered_set<std::unique_ptr<SENode>, SENodeHash, NodePointersEquality> | |
| node_cache_; | |
| // Loops that should be considered the same for performing analysis for loop | |
| // fusion. | |
| std::map<const Loop*, const Loop*> pretend_equal_; | |
| }; | |
| // Wrapping class to manipulate SENode pointer using + - * / operators. | |
| class SExpression { | |
| public: | |
| // Implicit on purpose ! | |
| SExpression(SENode* node) | |
| : node_(node->GetParentAnalysis()->SimplifyExpression(node)), | |
| scev_(node->GetParentAnalysis()) {} | |
| inline operator SENode*() const { return node_; } | |
| inline SENode* operator->() const { return node_; } | |
| const SENode& operator*() const { return *node_; } | |
| inline ScalarEvolutionAnalysis* GetScalarEvolutionAnalysis() const { | |
| return scev_; | |
| } | |
| inline SExpression operator+(SENode* rhs) const; | |
| template <typename T, | |
| typename std::enable_if<std::is_integral<T>::value, int>::type = 0> | |
| inline SExpression operator+(T integer) const; | |
| inline SExpression operator+(SExpression rhs) const; | |
| inline SExpression operator-() const; | |
| inline SExpression operator-(SENode* rhs) const; | |
| template <typename T, | |
| typename std::enable_if<std::is_integral<T>::value, int>::type = 0> | |
| inline SExpression operator-(T integer) const; | |
| inline SExpression operator-(SExpression rhs) const; | |
| inline SExpression operator*(SENode* rhs) const; | |
| template <typename T, | |
| typename std::enable_if<std::is_integral<T>::value, int>::type = 0> | |
| inline SExpression operator*(T integer) const; | |
| inline SExpression operator*(SExpression rhs) const; | |
| template <typename T, | |
| typename std::enable_if<std::is_integral<T>::value, int>::type = 0> | |
| inline std::pair<SExpression, int64_t> operator/(T integer) const; | |
| // Try to perform a division. Returns the pair <this.node_ / rhs, division | |
| // remainder>. If it fails to simplify it, the function returns a | |
| // CanNotCompute node. | |
| std::pair<SExpression, int64_t> operator/(SExpression rhs) const; | |
| private: | |
| SENode* node_; | |
| ScalarEvolutionAnalysis* scev_; | |
| }; | |
| inline SExpression SExpression::operator+(SENode* rhs) const { | |
| return scev_->CreateAddNode(node_, rhs); | |
| } | |
| template <typename T, | |
| typename std::enable_if<std::is_integral<T>::value, int>::type> | |
| inline SExpression SExpression::operator+(T integer) const { | |
| return *this + scev_->CreateConstant(integer); | |
| } | |
| inline SExpression SExpression::operator+(SExpression rhs) const { | |
| return *this + rhs.node_; | |
| } | |
| inline SExpression SExpression::operator-() const { | |
| return scev_->CreateNegation(node_); | |
| } | |
| inline SExpression SExpression::operator-(SENode* rhs) const { | |
| return *this + scev_->CreateNegation(rhs); | |
| } | |
| template <typename T, | |
| typename std::enable_if<std::is_integral<T>::value, int>::type> | |
| inline SExpression SExpression::operator-(T integer) const { | |
| return *this - scev_->CreateConstant(integer); | |
| } | |
| inline SExpression SExpression::operator-(SExpression rhs) const { | |
| return *this - rhs.node_; | |
| } | |
| inline SExpression SExpression::operator*(SENode* rhs) const { | |
| return scev_->CreateMultiplyNode(node_, rhs); | |
| } | |
| template <typename T, | |
| typename std::enable_if<std::is_integral<T>::value, int>::type> | |
| inline SExpression SExpression::operator*(T integer) const { | |
| return *this * scev_->CreateConstant(integer); | |
| } | |
| inline SExpression SExpression::operator*(SExpression rhs) const { | |
| return *this * rhs.node_; | |
| } | |
| template <typename T, | |
| typename std::enable_if<std::is_integral<T>::value, int>::type> | |
| inline std::pair<SExpression, int64_t> SExpression::operator/(T integer) const { | |
| return *this / scev_->CreateConstant(integer); | |
| } | |
| template <typename T, | |
| typename std::enable_if<std::is_integral<T>::value, int>::type> | |
| inline SExpression operator+(T lhs, SExpression rhs) { | |
| return rhs + lhs; | |
| } | |
| inline SExpression operator+(SENode* lhs, SExpression rhs) { return rhs + lhs; } | |
| template <typename T, | |
| typename std::enable_if<std::is_integral<T>::value, int>::type> | |
| inline SExpression operator-(T lhs, SExpression rhs) { | |
| // NOLINTNEXTLINE(whitespace/braces) | |
| return SExpression{rhs.GetScalarEvolutionAnalysis()->CreateConstant(lhs)} - | |
| rhs; | |
| } | |
| inline SExpression operator-(SENode* lhs, SExpression rhs) { | |
| // NOLINTNEXTLINE(whitespace/braces) | |
| return SExpression{lhs} - rhs; | |
| } | |
| template <typename T, | |
| typename std::enable_if<std::is_integral<T>::value, int>::type> | |
| inline SExpression operator*(T lhs, SExpression rhs) { | |
| return rhs * lhs; | |
| } | |
| inline SExpression operator*(SENode* lhs, SExpression rhs) { return rhs * lhs; } | |
| template <typename T, | |
| typename std::enable_if<std::is_integral<T>::value, int>::type> | |
| inline std::pair<SExpression, int64_t> operator/(T lhs, SExpression rhs) { | |
| // NOLINTNEXTLINE(whitespace/braces) | |
| return SExpression{rhs.GetScalarEvolutionAnalysis()->CreateConstant(lhs)} / | |
| rhs; | |
| } | |
| inline std::pair<SExpression, int64_t> operator/(SENode* lhs, SExpression rhs) { | |
| // NOLINTNEXTLINE(whitespace/braces) | |
| return SExpression{lhs} / rhs; | |
| } | |
| } // namespace opt | |
| } // namespace spvtools | |
| #endif // SOURCE_OPT_SCALAR_ANALYSIS_H_ |