• 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 // This file implements conditional constant propagation as described in
16 //
17 //      Constant propagation with conditional branches,
18 //      Wegman and Zadeck, ACM TOPLAS 13(2):181-210.
19 
20 #include "source/opt/ccp_pass.h"
21 
22 #include <algorithm>
23 #include <limits>
24 
25 #include "source/opt/fold.h"
26 #include "source/opt/function.h"
27 #include "source/opt/propagator.h"
28 
29 namespace spvtools {
30 namespace opt {
31 namespace {
32 // This SSA id is never defined nor referenced in the IR.  It is a special ID
33 // which represents varying values.  When an ID is found to have a varying
34 // value, its entry in the |values_| table maps to kVaryingSSAId.
35 constexpr uint32_t kVaryingSSAId = std::numeric_limits<uint32_t>::max();
36 }  // namespace
37 
IsVaryingValue(uint32_t id) const38 bool CCPPass::IsVaryingValue(uint32_t id) const { return id == kVaryingSSAId; }
39 
MarkInstructionVarying(Instruction * instr)40 SSAPropagator::PropStatus CCPPass::MarkInstructionVarying(Instruction* instr) {
41   assert(instr->result_id() != 0 &&
42          "Instructions with no result cannot be marked varying.");
43   values_[instr->result_id()] = kVaryingSSAId;
44   return SSAPropagator::kVarying;
45 }
46 
VisitPhi(Instruction * phi)47 SSAPropagator::PropStatus CCPPass::VisitPhi(Instruction* phi) {
48   uint32_t meet_val_id = 0;
49 
50   // Implement the lattice meet operation. The result of this Phi instruction is
51   // interesting only if the meet operation over arguments coming through
52   // executable edges yields the same constant value.
53   for (uint32_t i = 2; i < phi->NumOperands(); i += 2) {
54     if (!propagator_->IsPhiArgExecutable(phi, i)) {
55       // Ignore arguments coming through non-executable edges.
56       continue;
57     }
58     uint32_t phi_arg_id = phi->GetSingleWordOperand(i);
59     auto it = values_.find(phi_arg_id);
60     if (it != values_.end()) {
61       // We found an argument with a constant value.  Apply the meet operation
62       // with the previous arguments.
63       if (it->second == kVaryingSSAId) {
64         // The "constant" value is actually a placeholder for varying. Return
65         // varying for this phi.
66         return MarkInstructionVarying(phi);
67       } else if (meet_val_id == 0) {
68         // This is the first argument we find.  Initialize the result to its
69         // constant value id.
70         meet_val_id = it->second;
71       } else if (it->second == meet_val_id) {
72         // The argument is the same constant value already computed. Continue
73         // looking.
74         continue;
75       } else {
76         // We either found a varying value, or another constant value different
77         // from the previous computed meet value.  This Phi will never be
78         // constant.
79         return MarkInstructionVarying(phi);
80       }
81     } else {
82       // The incoming value has no recorded value and is therefore not
83       // interesting. A not interesting value joined with any other value is the
84       // other value.
85       continue;
86     }
87   }
88 
89   // If there are no incoming executable edges, the meet ID will still be 0. In
90   // that case, return not interesting to evaluate the Phi node again.
91   if (meet_val_id == 0) {
92     return SSAPropagator::kNotInteresting;
93   }
94 
95   // All the operands have the same constant value represented by |meet_val_id|.
96   // Set the Phi's result to that value and declare it interesting.
97   values_[phi->result_id()] = meet_val_id;
98   return SSAPropagator::kInteresting;
99 }
100 
ComputeLatticeMeet(Instruction * instr,uint32_t val2)101 uint32_t CCPPass::ComputeLatticeMeet(Instruction* instr, uint32_t val2) {
102   // Given two values val1 and val2, the meet operation in the constant
103   // lattice uses the following rules:
104   //
105   // meet(val1, UNDEFINED) = val1
106   // meet(val1, VARYING)   = VARYING
107   // meet(val1, val2)      = val1     if val1 == val2
108   // meet(val1, val2)      = VARYING  if val1 != val2
109   //
110   // When two different values meet, the result is always varying because CCP
111   // does not allow lateral transitions in the lattice.  This prevents
112   // infinite cycles during propagation.
113   auto val1_it = values_.find(instr->result_id());
114   if (val1_it == values_.end()) {
115     return val2;
116   }
117 
118   uint32_t val1 = val1_it->second;
119   if (IsVaryingValue(val1)) {
120     return val1;
121   } else if (IsVaryingValue(val2)) {
122     return val2;
123   } else if (val1 != val2) {
124     return kVaryingSSAId;
125   }
126   return val2;
127 }
128 
VisitAssignment(Instruction * instr)129 SSAPropagator::PropStatus CCPPass::VisitAssignment(Instruction* instr) {
130   assert(instr->result_id() != 0 &&
131          "Expecting an instruction that produces a result");
132 
133   // If this is a copy operation, and the RHS is a known constant, assign its
134   // value to the LHS.
135   if (instr->opcode() == spv::Op::OpCopyObject) {
136     uint32_t rhs_id = instr->GetSingleWordInOperand(0);
137     auto it = values_.find(rhs_id);
138     if (it != values_.end()) {
139       if (IsVaryingValue(it->second)) {
140         return MarkInstructionVarying(instr);
141       } else {
142         uint32_t new_val = ComputeLatticeMeet(instr, it->second);
143         values_[instr->result_id()] = new_val;
144         return IsVaryingValue(new_val) ? SSAPropagator::kVarying
145                                        : SSAPropagator::kInteresting;
146       }
147     }
148     return SSAPropagator::kNotInteresting;
149   }
150 
151   // Instructions with a RHS that cannot produce a constant are always varying.
152   if (!instr->IsFoldable()) {
153     return MarkInstructionVarying(instr);
154   }
155 
156   // See if the RHS of the assignment folds into a constant value.
157   auto map_func = [this](uint32_t id) {
158     auto it = values_.find(id);
159     if (it == values_.end() || IsVaryingValue(it->second)) {
160       return id;
161     }
162     return it->second;
163   };
164   Instruction* folded_inst =
165       context()->get_instruction_folder().FoldInstructionToConstant(instr,
166                                                                     map_func);
167 
168   if (folded_inst != nullptr) {
169     // We do not want to change the body of the function by adding new
170     // instructions.  When folding we can only generate new constants.
171     assert((folded_inst->IsConstant() ||
172             IsSpecConstantInst(folded_inst->opcode())) &&
173            "CCP is only interested in constant values.");
174     uint32_t new_val = ComputeLatticeMeet(instr, folded_inst->result_id());
175     values_[instr->result_id()] = new_val;
176     return IsVaryingValue(new_val) ? SSAPropagator::kVarying
177                                    : SSAPropagator::kInteresting;
178   }
179 
180   // Conservatively mark this instruction as varying if any input id is varying.
181   if (!instr->WhileEachInId([this](uint32_t* op_id) {
182         auto iter = values_.find(*op_id);
183         if (iter != values_.end() && IsVaryingValue(iter->second)) return false;
184         return true;
185       })) {
186     return MarkInstructionVarying(instr);
187   }
188 
189   // If not, see if there is a least one unknown operand to the instruction.  If
190   // so, we might be able to fold it later.
191   if (!instr->WhileEachInId([this](uint32_t* op_id) {
192         auto it = values_.find(*op_id);
193         if (it == values_.end()) return false;
194         return true;
195       })) {
196     return SSAPropagator::kNotInteresting;
197   }
198 
199   // Otherwise, we will never be able to fold this instruction, so mark it
200   // varying.
201   return MarkInstructionVarying(instr);
202 }
203 
VisitBranch(Instruction * instr,BasicBlock ** dest_bb) const204 SSAPropagator::PropStatus CCPPass::VisitBranch(Instruction* instr,
205                                                BasicBlock** dest_bb) const {
206   assert(instr->IsBranch() && "Expected a branch instruction.");
207 
208   *dest_bb = nullptr;
209   uint32_t dest_label = 0;
210   if (instr->opcode() == spv::Op::OpBranch) {
211     // An unconditional jump always goes to its unique destination.
212     dest_label = instr->GetSingleWordInOperand(0);
213   } else if (instr->opcode() == spv::Op::OpBranchConditional) {
214     // For a conditional branch, determine whether the predicate selector has a
215     // known value in |values_|.  If it does, set the destination block
216     // according to the selector's boolean value.
217     uint32_t pred_id = instr->GetSingleWordOperand(0);
218     auto it = values_.find(pred_id);
219     if (it == values_.end() || IsVaryingValue(it->second)) {
220       // The predicate has an unknown value, either branch could be taken.
221       return SSAPropagator::kVarying;
222     }
223 
224     // Get the constant value for the predicate selector from the value table.
225     // Use it to decide which branch will be taken.
226     uint32_t pred_val_id = it->second;
227     const analysis::Constant* c = const_mgr_->FindDeclaredConstant(pred_val_id);
228     assert(c && "Expected to find a constant declaration for a known value.");
229     // Undef values should have returned as varying above.
230     assert(c->AsBoolConstant() || c->AsNullConstant());
231     if (c->AsNullConstant()) {
232       dest_label = instr->GetSingleWordOperand(2u);
233     } else {
234       const analysis::BoolConstant* val = c->AsBoolConstant();
235       dest_label = val->value() ? instr->GetSingleWordOperand(1)
236                                 : instr->GetSingleWordOperand(2);
237     }
238   } else {
239     // For an OpSwitch, extract the value taken by the switch selector and check
240     // which of the target literals it matches.  The branch associated with that
241     // literal is the taken branch.
242     assert(instr->opcode() == spv::Op::OpSwitch);
243     if (instr->GetOperand(0).words.size() != 1) {
244       // If the selector is wider than 32-bits, return varying. TODO(dnovillo):
245       // Add support for wider constants.
246       return SSAPropagator::kVarying;
247     }
248     uint32_t select_id = instr->GetSingleWordOperand(0);
249     auto it = values_.find(select_id);
250     if (it == values_.end() || IsVaryingValue(it->second)) {
251       // The selector has an unknown value, any of the branches could be taken.
252       return SSAPropagator::kVarying;
253     }
254 
255     // Get the constant value for the selector from the value table. Use it to
256     // decide which branch will be taken.
257     uint32_t select_val_id = it->second;
258     const analysis::Constant* c =
259         const_mgr_->FindDeclaredConstant(select_val_id);
260     assert(c && "Expected to find a constant declaration for a known value.");
261     // TODO: support 64-bit integer switches.
262     uint32_t constant_cond = 0;
263     if (const analysis::IntConstant* val = c->AsIntConstant()) {
264       constant_cond = val->words()[0];
265     } else {
266       // Undef values should have returned varying above.
267       assert(c->AsNullConstant());
268       constant_cond = 0;
269     }
270 
271     // Start assuming that the selector will take the default value;
272     dest_label = instr->GetSingleWordOperand(1);
273     for (uint32_t i = 2; i < instr->NumOperands(); i += 2) {
274       if (constant_cond == instr->GetSingleWordOperand(i)) {
275         dest_label = instr->GetSingleWordOperand(i + 1);
276         break;
277       }
278     }
279   }
280 
281   assert(dest_label && "Destination label should be set at this point.");
282   *dest_bb = context()->cfg()->block(dest_label);
283   return SSAPropagator::kInteresting;
284 }
285 
VisitInstruction(Instruction * instr,BasicBlock ** dest_bb)286 SSAPropagator::PropStatus CCPPass::VisitInstruction(Instruction* instr,
287                                                     BasicBlock** dest_bb) {
288   *dest_bb = nullptr;
289   if (instr->opcode() == spv::Op::OpPhi) {
290     return VisitPhi(instr);
291   } else if (instr->IsBranch()) {
292     return VisitBranch(instr, dest_bb);
293   } else if (instr->result_id()) {
294     return VisitAssignment(instr);
295   }
296   return SSAPropagator::kVarying;
297 }
298 
ReplaceValues()299 bool CCPPass::ReplaceValues() {
300   // Even if we make no changes to the function's IR, propagation may have
301   // created new constants.  Even if those constants cannot be replaced in
302   // the IR, the constant definition itself is a change.  To reflect this,
303   // we check whether the next ID to be given by the module is different than
304   // the original bound ID. If that happens, new instructions were added to the
305   // module during propagation.
306   //
307   // See https://github.com/KhronosGroup/SPIRV-Tools/issues/3636 and
308   // https://github.com/KhronosGroup/SPIRV-Tools/issues/3991 for details.
309   bool changed_ir = (context()->module()->IdBound() > original_id_bound_);
310 
311   for (const auto& it : values_) {
312     uint32_t id = it.first;
313     uint32_t cst_id = it.second;
314     if (!IsVaryingValue(cst_id) && id != cst_id) {
315       context()->KillNamesAndDecorates(id);
316       changed_ir |= context()->ReplaceAllUsesWith(id, cst_id);
317     }
318   }
319 
320   return changed_ir;
321 }
322 
PropagateConstants(Function * fp)323 bool CCPPass::PropagateConstants(Function* fp) {
324   if (fp->IsDeclaration()) {
325     return false;
326   }
327 
328   // Mark function parameters as varying.
329   fp->ForEachParam([this](const Instruction* inst) {
330     values_[inst->result_id()] = kVaryingSSAId;
331   });
332 
333   const auto visit_fn = [this](Instruction* instr, BasicBlock** dest_bb) {
334     return VisitInstruction(instr, dest_bb);
335   };
336 
337   propagator_ =
338       std::unique_ptr<SSAPropagator>(new SSAPropagator(context(), visit_fn));
339 
340   if (propagator_->Run(fp)) {
341     return ReplaceValues();
342   }
343 
344   return false;
345 }
346 
Initialize()347 void CCPPass::Initialize() {
348   const_mgr_ = context()->get_constant_mgr();
349 
350   // Populate the constant table with values from constant declarations in the
351   // module.  The values of each OpConstant declaration is the identity
352   // assignment (i.e., each constant is its own value).
353   for (const auto& inst : get_module()->types_values()) {
354     // Record compile time constant ids. Treat all other global values as
355     // varying.
356     if (inst.IsConstant()) {
357       values_[inst.result_id()] = inst.result_id();
358     } else {
359       values_[inst.result_id()] = kVaryingSSAId;
360     }
361   }
362 
363   original_id_bound_ = context()->module()->IdBound();
364 }
365 
Process()366 Pass::Status CCPPass::Process() {
367   Initialize();
368 
369   // Process all entry point functions.
370   ProcessFunction pfn = [this](Function* fp) { return PropagateConstants(fp); };
371   bool modified = context()->ProcessReachableCallTree(pfn);
372   return modified ? Pass::Status::SuccessWithChange
373                   : Pass::Status::SuccessWithoutChange;
374 }
375 
376 }  // namespace opt
377 }  // namespace spvtools
378