• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2017 Google Inc.
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_PROPAGATOR_H_
16 #define SOURCE_OPT_PROPAGATOR_H_
17 
18 #include <functional>
19 #include <queue>
20 #include <set>
21 #include <unordered_map>
22 #include <unordered_set>
23 #include <utility>
24 #include <vector>
25 
26 #include "source/opt/ir_context.h"
27 #include "source/opt/module.h"
28 
29 namespace spvtools {
30 namespace opt {
31 
32 // Represents a CFG control edge.
33 struct Edge {
EdgeEdge34   Edge(BasicBlock* b1, BasicBlock* b2) : source(b1), dest(b2) {
35     assert(source && "CFG edges cannot have a null source block.");
36     assert(dest && "CFG edges cannot have a null destination block.");
37   }
38   BasicBlock* source;
39   BasicBlock* dest;
40   bool operator<(const Edge& o) const {
41     return std::make_pair(source->id(), dest->id()) <
42            std::make_pair(o.source->id(), o.dest->id());
43   }
44 };
45 
46 // This class implements a generic value propagation algorithm based on the
47 // conditional constant propagation algorithm proposed in
48 //
49 //      Constant propagation with conditional branches,
50 //      Wegman and Zadeck, ACM TOPLAS 13(2):181-210.
51 //
52 //      A Propagation Engine for GCC
53 //      Diego Novillo, GCC Summit 2005
54 //      http://ols.fedoraproject.org/GCC/Reprints-2005/novillo-Reprint.pdf
55 //
56 // The purpose of this implementation is to act as a common framework for any
57 // transformation that needs to propagate values from statements producing new
58 // values to statements using those values.  Simulation proceeds as follows:
59 //
60 // 1- Initially, all edges of the CFG are marked not executable and the CFG
61 //    worklist is seeded with all the statements in the entry basic block.
62 //
63 // 2- Every instruction I is simulated by calling a pass-provided function
64 //    |visit_fn|. This function is responsible for three things:
65 //
66 //    (a) Keep a value table of interesting values.  This table maps SSA IDs to
67 //        their values.  For instance, when implementing constant propagation,
68 //        given a store operation 'OpStore %f %int_3', |visit_fn| should assign
69 //        the value 3 to the table slot for %f.
70 //
71 //        In general, |visit_fn| will need to use the value table to replace its
72 //        operands, fold the result and decide whether a new value needs to be
73 //        stored in the table. |visit_fn| should only create a new mapping in
74 //        the value table if all the operands in the instruction are known and
75 //        present in the value table.
76 //
77 //    (b) Return a status indicator to direct the propagator logic.  Once the
78 //        instruction is simulated, the propagator needs to know whether this
79 //        instruction produced something interesting.  This is indicated via
80 //        |visit_fn|'s return value:
81 //
82 //         SSAPropagator::kNotInteresting: Instruction I produces nothing of
83 //             interest and does not affect any of the work lists.  The
84 //             propagator will visit the statement again if any of its operands
85 //             produce an interesting value in the future.
86 //
87 //             |visit_fn| should always return this value when it is not sure
88 //             whether the instruction will produce an interesting value in the
89 //             future or not.  For instance, for constant propagation, an OpIAdd
90 //             instruction may produce a constant if its two operands are
91 //             constant, but the first time we visit the instruction, we still
92 //             may not have its operands in the value table.
93 //
94 //         SSAPropagator::kVarying: The value produced by I cannot be determined
95 //             at compile time.  Further simulation of I is not required.  The
96 //             propagator will not visit this instruction again.  Additionally,
97 //             the propagator will add all the instructions at the end of SSA
98 //             def-use edges to be simulated again.
99 //
100 //             If I is a basic block terminator, it will mark all outgoing edges
101 //             as executable so they are traversed one more time.  Eventually
102 //             the kVarying attribute will be spread out to all the data and
103 //             control dependents for I.
104 //
105 //             It is important for propagation to use kVarying as a bottom value
106 //             for the propagation lattice.  It should never be possible for an
107 //             instruction to return kVarying once and kInteresting on a second
108 //             visit.  Otherwise, propagation would not stabilize.
109 //
110 //         SSAPropagator::kInteresting: Instruction I produces a value that can
111 //             be computed at compile time.  In this case, |visit_fn| should
112 //             create a new mapping between I's result ID and the produced
113 //             value.  Much like the kNotInteresting case, the propagator will
114 //             visit this instruction again if any of its operands changes.
115 //             This is useful when the statement changes from one interesting
116 //             state to another.
117 //
118 //    (c) For conditional branches, |visit_fn| may decide which edge to take out
119 //        of I's basic block.  For example, if the operand for an OpSwitch is
120 //        known to take a specific constant value, |visit_fn| should figure out
121 //        the destination basic block and pass it back by setting the second
122 //        argument to |visit_fn|.
123 //
124 //    At the end of propagation, values in the value table are guaranteed to be
125 //    stable and can be replaced in the IR.
126 //
127 // 3- The propagator keeps two work queues.  Instructions are only added to
128 //    these queues if they produce an interesting or varying value. None of this
129 //    should be handled by |visit_fn|. The propagator keeps track of this
130 //    automatically (see SSAPropagator::Simulate for implementation).
131 //
132 //      CFG blocks: contains the queue of blocks to be simulated.
133 //             Blocks are added to this queue if their incoming edges are
134 //             executable.
135 //
136 //      SSA Edges: An SSA edge is a def-use edge between a value-producing
137 //              instruction and its use instruction.  The SSA edges list
138 //              contains the statements at the end of a def-use edge that need
139 //              to be re-visited when an instruction produces a kVarying or
140 //              kInteresting result.
141 //
142 // 4- Simulation terminates when all work queues are drained.
143 //
144 //
145 // EXAMPLE: Basic constant store propagator.
146 //
147 // Suppose we want to propagate all constant assignments of the form "OpStore
148 // %id %cst" where "%id" is some variable and "%cst" an OpConstant.  The
149 // following code builds a table |values| where every id that was assigned a
150 // constant value is mapped to the constant value it was assigned.
151 //
152 //   auto ctx = BuildModule(...);
153 //   std::map<uint32_t, uint32_t> values;
154 //   const auto visit_fn = [&ctx, &values](Instruction* instr,
155 //                                         BasicBlock** dest_bb) {
156 //     if (instr->opcode() == SpvOpStore) {
157 //       uint32_t rhs_id = instr->GetSingleWordOperand(1);
158 //       Instruction* rhs_def = ctx->get_def_use_mgr()->GetDef(rhs_id);
159 //       if (rhs_def->opcode() == SpvOpConstant) {
160 //         uint32_t val = rhs_def->GetSingleWordOperand(2);
161 //         values[rhs_id] = val;
162 //         return SSAPropagator::kInteresting;
163 //       }
164 //     }
165 //     return SSAPropagator::kVarying;
166 //   };
167 //   SSAPropagator propagator(ctx.get(), &cfg, visit_fn);
168 //   propagator.Run(&fn);
169 //
170 // Given the code:
171 //
172 //       %int_4 = OpConstant %int 4
173 //       %int_3 = OpConstant %int 3
174 //       %int_1 = OpConstant %int 1
175 //                OpStore %x %int_4
176 //                OpStore %y %int_3
177 //                OpStore %z %int_1
178 //
179 // After SSAPropagator::Run returns, the |values| map will contain the entries:
180 // values[%x] = 4, values[%y] = 3, and, values[%z] = 1.
181 class SSAPropagator {
182  public:
183   // Lattice values used for propagation. See class documentation for
184   // a description.
185   enum PropStatus { kNotInteresting, kInteresting, kVarying };
186 
187   using VisitFunction = std::function<PropStatus(Instruction*, BasicBlock**)>;
188 
SSAPropagator(IRContext * context,const VisitFunction & visit_fn)189   SSAPropagator(IRContext* context, const VisitFunction& visit_fn)
190       : ctx_(context), visit_fn_(visit_fn) {}
191 
192   // Runs the propagator on function |fn|. Returns true if changes were made to
193   // the function. Otherwise, it returns false.
194   bool Run(Function* fn);
195 
196   // Returns true if the |i|th argument for |phi| comes through a CFG edge that
197   // has been marked executable. |i| should be an index value accepted by
198   // Instruction::GetSingleWordOperand.
199   bool IsPhiArgExecutable(Instruction* phi, uint32_t i) const;
200 
201   // Returns true if |inst| has a recorded status. This will be true once |inst|
202   // has been simulated once.
HasStatus(Instruction * inst)203   bool HasStatus(Instruction* inst) const { return statuses_.count(inst); }
204 
205   // Returns the current propagation status of |inst|. Assumes
206   // |HasStatus(inst)| returns true.
Status(Instruction * inst)207   PropStatus Status(Instruction* inst) const {
208     return statuses_.find(inst)->second;
209   }
210 
211   // Records the propagation status |status| for |inst|. Returns true if the
212   // status for |inst| has changed or set was set for the first time.
213   bool SetStatus(Instruction* inst, PropStatus status);
214 
215  private:
216   // Initialize processing.
217   void Initialize(Function* fn);
218 
219   // Simulate the execution |block| by calling |visit_fn_| on every instruction
220   // in it.
221   bool Simulate(BasicBlock* block);
222 
223   // Simulate the execution of |instr| by replacing all the known values in
224   // every operand and determining whether the result is interesting for
225   // propagation. This invokes the callback function |visit_fn_| to determine
226   // the value computed by |instr|.
227   bool Simulate(Instruction* instr);
228 
229   // Returns true if |instr| should be simulated again.
ShouldSimulateAgain(Instruction * instr)230   bool ShouldSimulateAgain(Instruction* instr) const {
231     return do_not_simulate_.find(instr) == do_not_simulate_.end();
232   }
233 
234   // Add |instr| to the set of instructions not to simulate again.
DontSimulateAgain(Instruction * instr)235   void DontSimulateAgain(Instruction* instr) { do_not_simulate_.insert(instr); }
236 
237   // Returns true if |block| has been simulated already.
BlockHasBeenSimulated(BasicBlock * block)238   bool BlockHasBeenSimulated(BasicBlock* block) const {
239     return simulated_blocks_.find(block) != simulated_blocks_.end();
240   }
241 
242   // Marks block |block| as simulated.
MarkBlockSimulated(BasicBlock * block)243   void MarkBlockSimulated(BasicBlock* block) {
244     simulated_blocks_.insert(block);
245   }
246 
247   // Marks |edge| as executable.  Returns false if the edge was already marked
248   // as executable.
MarkEdgeExecutable(const Edge & edge)249   bool MarkEdgeExecutable(const Edge& edge) {
250     return executable_edges_.insert(edge).second;
251   }
252 
253   // Returns true if |edge| has been marked as executable.
IsEdgeExecutable(const Edge & edge)254   bool IsEdgeExecutable(const Edge& edge) const {
255     return executable_edges_.find(edge) != executable_edges_.end();
256   }
257 
258   // Returns a pointer to the def-use manager for |ctx_|.
get_def_use_mgr()259   analysis::DefUseManager* get_def_use_mgr() const {
260     return ctx_->get_def_use_mgr();
261   }
262 
263   // If the CFG edge |e| has not been executed, this function adds |e|'s
264   // destination block to the work list.
265   void AddControlEdge(const Edge& e);
266 
267   // Adds all the instructions that use the result of |instr| to the SSA edges
268   // work list. If |instr| produces no result id, this does nothing.
269   void AddSSAEdges(Instruction* instr);
270 
271   // IR context to use.
272   IRContext* ctx_;
273 
274   // Function that visits instructions during simulation. The output of this
275   // function is used to determine if the simulated instruction produced a value
276   // interesting for propagation. The function is responsible for keeping
277   // track of interesting values by storing them in some user-provided map.
278   VisitFunction visit_fn_;
279 
280   // SSA def-use edges to traverse. Each entry is a destination statement for an
281   // SSA def-use edge as returned by |def_use_manager_|.
282   std::queue<Instruction*> ssa_edge_uses_;
283 
284   // Blocks to simulate.
285   std::queue<BasicBlock*> blocks_;
286 
287   // Blocks simulated during propagation.
288   std::unordered_set<BasicBlock*> simulated_blocks_;
289 
290   // Set of instructions that should not be simulated again because they have
291   // been found to be in the kVarying state.
292   std::unordered_set<Instruction*> do_not_simulate_;
293 
294   // Map between a basic block and its predecessor edges.
295   // TODO(dnovillo): Move this to CFG and always build them. Alternately,
296   // move it to IRContext and build CFG preds/succs on-demand.
297   std::unordered_map<BasicBlock*, std::vector<Edge>> bb_preds_;
298 
299   // Map between a basic block and its successor edges.
300   // TODO(dnovillo): Move this to CFG and always build them. Alternately,
301   // move it to IRContext and build CFG preds/succs on-demand.
302   std::unordered_map<BasicBlock*, std::vector<Edge>> bb_succs_;
303 
304   // Set of executable CFG edges.
305   std::set<Edge> executable_edges_;
306 
307   // Tracks instruction propagation status.
308   std::unordered_map<Instruction*, SSAPropagator::PropStatus> statuses_;
309 };
310 
311 std::ostream& operator<<(std::ostream& str,
312                          const SSAPropagator::PropStatus& status);
313 
314 }  // namespace opt
315 }  // namespace spvtools
316 
317 #endif  // SOURCE_OPT_PROPAGATOR_H_
318