summaryrefslogtreecommitdiff
path: root/source/opt/propagator.h
blob: aef21de4d9e59ca450a45ba1e9e8326171daf03d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
// Copyright (c) 2017 Google Inc.
//
// 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 LIBSPIRV_OPT_PROPAGATOR_H_
#define LIBSPIRV_OPT_PROPAGATOR_H_

#include <functional>
#include <queue>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "ir_context.h"
#include "module.h"

namespace spvtools {
namespace opt {

// Represents a CFG control edge.
struct Edge {
  Edge(BasicBlock* b1, BasicBlock* b2) : source(b1), dest(b2) {
    assert(source && "CFG edges cannot have a null source block.");
    assert(dest && "CFG edges cannot have a null destination block.");
  }
  BasicBlock* source;
  BasicBlock* dest;
  bool operator<(const Edge& o) const {
    return std::make_pair(source->id(), dest->id()) <
           std::make_pair(o.source->id(), o.dest->id());
  }
};

// This class implements a generic value propagation algorithm based on the
// conditional constant propagation algorithm proposed in
//
//      Constant propagation with conditional branches,
//      Wegman and Zadeck, ACM TOPLAS 13(2):181-210.
//
//      A Propagation Engine for GCC
//      Diego Novillo, GCC Summit 2005
//      http://ols.fedoraproject.org/GCC/Reprints-2005/novillo-Reprint.pdf
//
// The purpose of this implementation is to act as a common framework for any
// transformation that needs to propagate values from statements producing new
// values to statements using those values.  Simulation proceeds as follows:
//
// 1- Initially, all edges of the CFG are marked not executable and the CFG
//    worklist is seeded with all the statements in the entry basic block.
//
// 2- Every instruction I is simulated by calling a pass-provided function
//    |visit_fn|. This function is responsible for three things:
//
//    (a) Keep a value table of interesting values.  This table maps SSA IDs to
//        their values.  For instance, when implementing constant propagation,
//        given a store operation 'OpStore %f %int_3', |visit_fn| should assign
//        the value 3 to the table slot for %f.
//
//        In general, |visit_fn| will need to use the value table to replace its
//        operands, fold the result and decide whether a new value needs to be
//        stored in the table. |visit_fn| should only create a new mapping in
//        the value table if all the operands in the instruction are known and
//        present in the value table.
//
//    (b) Return a status indicator to direct the propagator logic.  Once the
//        instruction is simulated, the propagator needs to know whether this
//        instruction produced something interesting.  This is indicated via
//        |visit_fn|'s return value:
//
//         SSAPropagator::kNotInteresting: Instruction I produces nothing of
//             interest and does not affect any of the work lists.  The
//             propagator will visit the statement again if any of its operands
//             produce an interesting value in the future.
//
//             |visit_fn| should always return this value when it is not sure
//             whether the instruction will produce an interesting value in the
//             future or not.  For instance, for constant propagation, an OpIAdd
//             instruction may produce a constant if its two operands are
//             constant, but the first time we visit the instruction, we still
//             may not have its operands in the value table.
//
//         SSAPropagator::kVarying: The value produced by I cannot be determined
//             at compile time.  Further simulation of I is not required.  The
//             propagator will not visit this instruction again.  Additionally,
//             the propagator will add all the instructions at the end of SSA
//             def-use edges to be simulated again.
//
//             If I is a basic block terminator, it will mark all outgoing edges
//             as executable so they are traversed one more time.  Eventually
//             the kVarying attribute will be spread out to all the data and
//             control dependents for I.
//
//             It is important for propagation to use kVarying as a bottom value
//             for the propagation lattice.  It should never be possible for an
//             instruction to return kVarying once and kInteresting on a second
//             visit.  Otherwise, propagation would not stabilize.
//
//         SSAPropagator::kInteresting: Instruction I produces a value that can
//             be computed at compile time.  In this case, |visit_fn| should
//             create a new mapping between I's result ID and the produced
//             value.  Much like the kNotInteresting case, the propagator will
//             visit this instruction again if any of its operands changes.
//             This is useful when the statement changes from one interesting
//             state to another.
//
//    (c) For conditional branches, |visit_fn| may decide which edge to take out
//        of I's basic block.  For example, if the operand for an OpSwitch is
//        known to take a specific constant value, |visit_fn| should figure out
//        the destination basic block and pass it back by setting the second
//        argument to |visit_fn|.
//
//    At the end of propagation, values in the value table are guaranteed to be
//    stable and can be replaced in the IR.
//
// 3- The propagator keeps two work queues.  Instructions are only added to
//    these queues if they produce an interesting or varying value. None of this
//    should be handled by |visit_fn|. The propagator keeps track of this
//    automatically (see SSAPropagator::Simulate for implementation).
//
//      CFG blocks: contains the queue of blocks to be simulated.
//             Blocks are added to this queue if their incoming edges are
//             executable.
//
//      SSA Edges: An SSA edge is a def-use edge between a value-producing
//              instruction and its use instruction.  The SSA edges list
//              contains the statements at the end of a def-use edge that need
//              to be re-visited when an instruction produces a kVarying or
//              kInteresting result.
//
// 4- Simulation terminates when all work queues are drained.
//
//
// EXAMPLE: Basic constant store propagator.
//
// Suppose we want to propagate all constant assignments of the form "OpStore
// %id %cst" where "%id" is some variable and "%cst" an OpConstant.  The
// following code builds a table |values| where every id that was assigned a
// constant value is mapped to the constant value it was assigned.
//
//   auto ctx = BuildModule(...);
//   std::map<uint32_t, uint32_t> values;
//   const auto visit_fn = [&ctx, &values](Instruction* instr,
//                                         BasicBlock** dest_bb) {
//     if (instr->opcode() == SpvOpStore) {
//       uint32_t rhs_id = instr->GetSingleWordOperand(1);
//       Instruction* rhs_def = ctx->get_def_use_mgr()->GetDef(rhs_id);
//       if (rhs_def->opcode() == SpvOpConstant) {
//         uint32_t val = rhs_def->GetSingleWordOperand(2);
//         values[rhs_id] = val;
//         return SSAPropagator::kInteresting;
//       }
//     }
//     return SSAPropagator::kVarying;
//   };
//   SSAPropagator propagator(ctx.get(), &cfg, visit_fn);
//   propagator.Run(&fn);
//
// Given the code:
//
//       %int_4 = OpConstant %int 4
//       %int_3 = OpConstant %int 3
//       %int_1 = OpConstant %int 1
//                OpStore %x %int_4
//                OpStore %y %int_3
//                OpStore %z %int_1
//
// After SSAPropagator::Run returns, the |values| map will contain the entries:
// values[%x] = 4, values[%y] = 3, and, values[%z] = 1.
class SSAPropagator {
 public:
  // Lattice values used for propagation. See class documentation for
  // a description.
  enum PropStatus { kNotInteresting, kInteresting, kVarying };

  using VisitFunction = std::function<PropStatus(Instruction*, BasicBlock**)>;

  SSAPropagator(IRContext* context, const VisitFunction& visit_fn)
      : ctx_(context), visit_fn_(visit_fn) {}

  // Runs the propagator on function |fn|. Returns true if changes were made to
  // the function. Otherwise, it returns false.
  bool Run(Function* fn);

  // Returns true if the |i|th argument for |phi| comes through a CFG edge that
  // has been marked executable. |i| should be an index value accepted by
  // Instruction::GetSingleWordOperand.
  bool IsPhiArgExecutable(Instruction* phi, uint32_t i) const;

  // Returns true if |inst| has a recorded status. This will be true once |inst|
  // has been simulated once.
  bool HasStatus(Instruction* inst) const { return statuses_.count(inst); }

  // Returns the current propagation status of |inst|. Assumes
  // |HasStatus(inst)| returns true.
  PropStatus Status(Instruction* inst) const {
    return statuses_.find(inst)->second;
  }

  // Records the propagation status |status| for |inst|. Returns true if the
  // status for |inst| has changed or set was set for the first time.
  bool SetStatus(Instruction* inst, PropStatus status);

 private:
  // Initialize processing.
  void Initialize(Function* fn);

  // Simulate the execution |block| by calling |visit_fn_| on every instruction
  // in it.
  bool Simulate(BasicBlock* block);

  // Simulate the execution of |instr| by replacing all the known values in
  // every operand and determining whether the result is interesting for
  // propagation. This invokes the callback function |visit_fn_| to determine
  // the value computed by |instr|.
  bool Simulate(Instruction* instr);

  // Returns true if |instr| should be simulated again.
  bool ShouldSimulateAgain(Instruction* instr) const {
    return do_not_simulate_.find(instr) == do_not_simulate_.end();
  }

  // Add |instr| to the set of instructions not to simulate again.
  void DontSimulateAgain(Instruction* instr) { do_not_simulate_.insert(instr); }

  // Returns true if |block| has been simulated already.
  bool BlockHasBeenSimulated(BasicBlock* block) const {
    return simulated_blocks_.find(block) != simulated_blocks_.end();
  }

  // Marks block |block| as simulated.
  void MarkBlockSimulated(BasicBlock* block) {
    simulated_blocks_.insert(block);
  }

  // Marks |edge| as executable.  Returns false if the edge was already marked
  // as executable.
  bool MarkEdgeExecutable(const Edge& edge) {
    return executable_edges_.insert(edge).second;
  }

  // Returns true if |edge| has been marked as executable.
  bool IsEdgeExecutable(const Edge& edge) const {
    return executable_edges_.find(edge) != executable_edges_.end();
  }

  // Returns a pointer to the def-use manager for |ctx_|.
  analysis::DefUseManager* get_def_use_mgr() const {
    return ctx_->get_def_use_mgr();
  }

  // If the CFG edge |e| has not been executed, this function adds |e|'s
  // destination block to the work list.
  void AddControlEdge(const Edge& e);

  // Adds all the instructions that use the result of |instr| to the SSA edges
  // work list. If |instr| produces no result id, this does nothing.
  void AddSSAEdges(Instruction* instr);

  // IR context to use.
  IRContext* ctx_;

  // Function that visits instructions during simulation. The output of this
  // function is used to determine if the simulated instruction produced a value
  // interesting for propagation. The function is responsible for keeping
  // track of interesting values by storing them in some user-provided map.
  VisitFunction visit_fn_;

  // SSA def-use edges to traverse. Each entry is a destination statement for an
  // SSA def-use edge as returned by |def_use_manager_|.
  std::queue<Instruction*> ssa_edge_uses_;

  // Blocks to simulate.
  std::queue<BasicBlock*> blocks_;

  // Blocks simulated during propagation.
  std::unordered_set<BasicBlock*> simulated_blocks_;

  // Set of instructions that should not be simulated again because they have
  // been found to be in the kVarying state.
  std::unordered_set<Instruction*> do_not_simulate_;

  // Map between a basic block and its predecessor edges.
  // TODO(dnovillo): Move this to CFG and always build them. Alternately,
  // move it to IRContext and build CFG preds/succs on-demand.
  std::unordered_map<BasicBlock*, std::vector<Edge>> bb_preds_;

  // Map between a basic block and its successor edges.
  // TODO(dnovillo): Move this to CFG and always build them. Alternately,
  // move it to IRContext and build CFG preds/succs on-demand.
  std::unordered_map<BasicBlock*, std::vector<Edge>> bb_succs_;

  // Set of executable CFG edges.
  std::set<Edge> executable_edges_;

  // Tracks instruction propagation status.
  std::unordered_map<Instruction*, SSAPropagator::PropStatus> statuses_;
};

std::ostream& operator<<(std::ostream& str,
                         const SSAPropagator::PropStatus& status);

}  // namespace opt
}  // namespace spvtools

#endif  // LIBSPIRV_OPT_PROPAGATOR_H_