1 // Copyright (c) 2021 Google LLC. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef SOURCE_OPT_DATAFLOW_H_ 16 #define SOURCE_OPT_DATAFLOW_H_ 17 18 #include <queue> 19 #include <unordered_map> 20 #include <vector> 21 22 #include "source/opt/instruction.h" 23 #include "source/opt/ir_context.h" 24 25 namespace spvtools { 26 namespace opt { 27 28 // Generic data-flow analysis. 29 // Maintains a worklist of instructions to process and processes them in a 30 // specified order. See also ForwardDataFlowAnalysis, which is specialized for 31 // forward data-flow analysis. 32 class DataFlowAnalysis { 33 public: 34 // The result of a |Visit| operation on an instruction. 35 // This is used to determine when analysis has reached a fixpoint. 36 enum class VisitResult { 37 // The analysis result for this instruction has changed. 38 // This means that any instructions that depend on it (its successors) must 39 // be recomputed. 40 kResultChanged, 41 // The analysis result for this instruction has not changed. 42 // When all visit operations return |kResultFixed|, the analysis has reached 43 // a fixpoint (converged). 44 kResultFixed, 45 }; 46 ~DataFlowAnalysis()47 virtual ~DataFlowAnalysis() {} 48 49 // Run this analysis on a given function. 50 // For analyses which work interprocedurally, |function| may be ignored. 51 void Run(Function* function); 52 53 protected: DataFlowAnalysis(IRContext & context)54 DataFlowAnalysis(IRContext& context) : context_(context) {} 55 56 // Initialize the worklist for a given function. 57 // |is_first_iteration| is true on the first call to |Run| and false 58 // afterwards. All subsequent runs are only necessary to check if the analysis 59 // has converged; if |EnqueueSuccessors| is complete, |InitializeWorklist| 60 // should do nothing after the first iteration. 61 virtual void InitializeWorklist(Function* function, 62 bool is_first_iteration) = 0; 63 64 // Enqueues the successors (instructions which use the analysis result) of 65 // |inst|. This is not required to be complete, but convergence is faster when 66 // it is. This is called whenever |Visit| returns |kResultChanged|. 67 virtual void EnqueueSuccessors(Instruction* inst) = 0; 68 69 // Visits the given instruction, recomputing the analysis result. This is 70 // called once per instruction queued in |InitializeWorklist| and afterward 71 // when a predecessor is changed, through |EnqueueSuccessors|. 72 virtual VisitResult Visit(Instruction* inst) = 0; 73 74 // Enqueues the given instruction to be visited. Ignored if already in the 75 // worklist. 76 bool Enqueue(Instruction* inst); 77 context()78 IRContext& context() { return context_; } 79 80 private: 81 // Runs one pass, calling |InitializeWorklist| and then iterating through the 82 // worklist until all fixed. 83 VisitResult RunOnce(Function* function, bool is_first_iteration); 84 85 IRContext& context_; 86 std::unordered_map<Instruction*, bool> on_worklist_; 87 // The worklist, which contains the list of instructions to be visited. 88 // 89 // The choice of data structure was influenced by the data in "Iterative 90 // Data-flow Analysis, Revisited" (Cooper et al, 2002). 91 // https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.125.1549&rep=rep1&type=pdf 92 // The paper shows that the overall performance benefit of a priority queue 93 // over a regular queue or stack is relatively small (or negative). 94 // 95 // A queue has the advantage that nodes are visited in the same order they are 96 // enqueued, which relieves the analysis from inserting nodes "backwards", for 97 // example in worklist initialization. Also, as the paper claims that sorting 98 // successors does not improve runtime, we can use a single queue which is 99 // modified during iteration. 100 std::queue<Instruction*> worklist_; 101 }; 102 103 // A generic data flow analysis, specialized for forward analysis. 104 class ForwardDataFlowAnalysis : public DataFlowAnalysis { 105 public: 106 // Indicates where labels should be in the worklist RPO ordering. 107 enum class LabelPosition { 108 // Labels should be placed at the beginning of their blocks. 109 kLabelsAtBeginning, 110 // Labels should be placed at the end of their blocks. 111 kLabelsAtEnd, 112 // Labels should not be in the worklist. 113 kNoLabels, 114 // Only labels should be placed in the worklist. 115 kLabelsOnly, 116 }; 117 ForwardDataFlowAnalysis(IRContext & context,LabelPosition label_position)118 ForwardDataFlowAnalysis(IRContext& context, LabelPosition label_position) 119 : DataFlowAnalysis(context), label_position_(label_position) {} 120 121 protected: 122 // Initializes the worklist in reverse postorder, regardless of 123 // |is_first_iteration|. Labels are placed according to the label position 124 // specified in the constructor. 125 void InitializeWorklist(Function* function, bool is_first_iteration) override; 126 127 // Enqueues the users and block successors of the given instruction. 128 // See |EnqueueUsers| and |EnqueueBlockSuccessors|. EnqueueSuccessors(Instruction * inst)129 void EnqueueSuccessors(Instruction* inst) override { 130 EnqueueUsers(inst); 131 EnqueueBlockSuccessors(inst); 132 } 133 134 // Enqueues the users of the given instruction. 135 void EnqueueUsers(Instruction* inst); 136 137 // Enqueues the labels of the successors of the block corresponding to the 138 // given label instruction. Does nothing for other instructions. 139 void EnqueueBlockSuccessors(Instruction* inst); 140 141 private: 142 LabelPosition label_position_; 143 }; 144 145 } // namespace opt 146 } // namespace spvtools 147 148 #endif // SOURCE_OPT_DATAFLOW_H_ 149