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