Coverage Report

Created: 2026-06-30 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/opt/ccp_pass.cpp
Line
Count
Source
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
38
5.11M
bool CCPPass::IsVaryingValue(uint32_t id) const { return id == kVaryingSSAId; }
39
40
1.54M
SSAPropagator::PropStatus CCPPass::MarkInstructionVarying(Instruction* instr) {
41
1.54M
  assert(instr->result_id() != 0 &&
42
1.54M
         "Instructions with no result cannot be marked varying.");
43
1.54M
  values_[instr->result_id()] = kVaryingSSAId;
44
1.54M
  return SSAPropagator::kVarying;
45
1.54M
}
46
47
404k
SSAPropagator::PropStatus CCPPass::VisitPhi(Instruction* phi) {
48
404k
  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
1.14M
  for (uint32_t i = 2; i < phi->NumOperands(); i += 2) {
54
924k
    if (!propagator_->IsPhiArgExecutable(phi, i)) {
55
      // Ignore arguments coming through non-executable edges.
56
394k
      continue;
57
394k
    }
58
529k
    uint32_t phi_arg_id = phi->GetSingleWordOperand(i);
59
529k
    auto it = values_.find(phi_arg_id);
60
529k
    if (it != values_.end()) {
61
      // We found an argument with a constant value.  Apply the meet operation
62
      // with the previous arguments.
63
529k
      if (it->second == kVaryingSSAId) {
64
        // The "constant" value is actually a placeholder for varying. Return
65
        // varying for this phi.
66
150k
        return MarkInstructionVarying(phi);
67
378k
      } else if (meet_val_id == 0) {
68
        // This is the first argument we find.  Initialize the result to its
69
        // constant value id.
70
272k
        meet_val_id = it->second;
71
272k
      } else if (it->second == meet_val_id) {
72
        // The argument is the same constant value already computed. Continue
73
        // looking.
74
71.0k
        continue;
75
71.0k
      } 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
35.4k
        return MarkInstructionVarying(phi);
80
35.4k
      }
81
529k
    } 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
0
      continue;
86
0
    }
87
529k
  }
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
218k
  if (meet_val_id == 0) {
92
0
    return SSAPropagator::kNotInteresting;
93
0
  }
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
218k
  values_[phi->result_id()] = meet_val_id;
98
218k
  return SSAPropagator::kInteresting;
99
218k
}
100
101
156k
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
156k
  auto val1_it = values_.find(instr->result_id());
114
156k
  if (val1_it == values_.end()) {
115
155k
    return val2;
116
155k
  }
117
118
1.67k
  uint32_t val1 = val1_it->second;
119
1.67k
  if (IsVaryingValue(val1)) {
120
0
    return val1;
121
1.67k
  } else if (IsVaryingValue(val2)) {
122
0
    return val2;
123
1.67k
  } else if (val1 != val2) {
124
206
    return kVaryingSSAId;
125
206
  }
126
1.46k
  return val2;
127
1.67k
}
128
129
1.51M
SSAPropagator::PropStatus CCPPass::VisitAssignment(Instruction* instr) {
130
1.51M
  assert(instr->result_id() != 0 &&
131
1.51M
         "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
1.51M
  if (instr->opcode() == spv::Op::OpCopyObject) {
136
692
    uint32_t rhs_id = instr->GetSingleWordInOperand(0);
137
692
    auto it = values_.find(rhs_id);
138
692
    if (it != values_.end()) {
139
692
      if (IsVaryingValue(it->second)) {
140
163
        return MarkInstructionVarying(instr);
141
529
      } else {
142
529
        uint32_t new_val = ComputeLatticeMeet(instr, it->second);
143
529
        values_[instr->result_id()] = new_val;
144
529
        return IsVaryingValue(new_val) ? SSAPropagator::kVarying
145
529
                                       : SSAPropagator::kInteresting;
146
529
      }
147
692
    }
148
0
    return SSAPropagator::kNotInteresting;
149
692
  }
150
151
  // Instructions with a RHS that cannot produce a constant are always varying.
152
1.51M
  if (!instr->IsFoldable()) {
153
879k
    return MarkInstructionVarying(instr);
154
879k
  }
155
156
  // See if the RHS of the assignment folds into a constant value.
157
1.83M
  auto map_func = [this](uint32_t id) {
158
1.83M
    auto it = values_.find(id);
159
1.83M
    if (it == values_.end() || IsVaryingValue(it->second)) {
160
1.11M
      return id;
161
1.11M
    }
162
714k
    return it->second;
163
1.83M
  };
164
639k
  Instruction* folded_inst =
165
639k
      context()->get_instruction_folder().FoldInstructionToConstant(instr,
166
639k
                                                                    map_func);
167
168
639k
  if (folded_inst && context()->id_overflow()) {
169
0
    return SSAPropagator::kFailed;
170
0
  }
171
639k
  if (folded_inst != nullptr) {
172
    // We do not want to change the body of the function by adding new
173
    // instructions.  When folding we can only generate new constants.
174
156k
    assert((folded_inst->IsConstant() ||
175
156k
            IsSpecConstantInst(folded_inst->opcode())) &&
176
156k
           "CCP is only interested in constant values.");
177
156k
    uint32_t new_val = ComputeLatticeMeet(instr, folded_inst->result_id());
178
156k
    values_[instr->result_id()] = new_val;
179
156k
    return IsVaryingValue(new_val) ? SSAPropagator::kVarying
180
156k
                                   : SSAPropagator::kInteresting;
181
156k
  }
182
183
  // Conservatively mark this instruction as varying if any input id is varying.
184
528k
  if (!instr->WhileEachInId([this](uint32_t* op_id) {
185
528k
        auto iter = values_.find(*op_id);
186
528k
        if (iter != values_.end() && IsVaryingValue(iter->second)) return false;
187
45.1k
        return true;
188
528k
      })) {
189
482k
    return MarkInstructionVarying(instr);
190
482k
  }
191
192
  // If not, see if there is a least one unknown operand to the instruction.  If
193
  // so, we might be able to fold it later.
194
393
  if (!instr->WhileEachInId([this](uint32_t* op_id) {
195
393
        auto it = values_.find(*op_id);
196
393
        if (it == values_.end()) return false;
197
393
        return true;
198
393
      })) {
199
0
    return SSAPropagator::kNotInteresting;
200
0
  }
201
202
  // Otherwise, we will never be able to fold this instruction, so mark it
203
  // varying.
204
166
  return MarkInstructionVarying(instr);
205
166
}
206
207
SSAPropagator::PropStatus CCPPass::VisitBranch(Instruction* instr,
208
645k
                                               BasicBlock** dest_bb) const {
209
645k
  assert(instr->IsBranch() && "Expected a branch instruction.");
210
211
645k
  *dest_bb = nullptr;
212
645k
  uint32_t dest_label = 0;
213
645k
  if (instr->opcode() == spv::Op::OpBranch) {
214
    // An unconditional jump always goes to its unique destination.
215
388k
    dest_label = instr->GetSingleWordInOperand(0);
216
388k
  } else if (instr->opcode() == spv::Op::OpBranchConditional) {
217
    // For a conditional branch, determine whether the predicate selector has a
218
    // known value in |values_|.  If it does, set the destination block
219
    // according to the selector's boolean value.
220
208k
    uint32_t pred_id = instr->GetSingleWordOperand(0);
221
208k
    auto it = values_.find(pred_id);
222
208k
    if (it == values_.end() || IsVaryingValue(it->second)) {
223
      // The predicate has an unknown value, either branch could be taken.
224
98.8k
      return SSAPropagator::kVarying;
225
98.8k
    }
226
227
    // Get the constant value for the predicate selector from the value table.
228
    // Use it to decide which branch will be taken.
229
109k
    uint32_t pred_val_id = it->second;
230
109k
    const analysis::Constant* c = const_mgr_->FindDeclaredConstant(pred_val_id);
231
109k
    assert(c && "Expected to find a constant declaration for a known value.");
232
    // Undef values should have returned as varying above.
233
109k
    assert(c->AsBoolConstant() || c->AsNullConstant());
234
109k
    if (c->AsNullConstant()) {
235
317
      dest_label = instr->GetSingleWordOperand(2u);
236
108k
    } else {
237
108k
      const analysis::BoolConstant* val = c->AsBoolConstant();
238
108k
      dest_label = val->value() ? instr->GetSingleWordOperand(1)
239
108k
                                : instr->GetSingleWordOperand(2);
240
108k
    }
241
109k
  } else {
242
    // For an OpSwitch, extract the value taken by the switch selector and check
243
    // which of the target literals it matches.  The branch associated with that
244
    // literal is the taken branch.
245
48.4k
    assert(instr->opcode() == spv::Op::OpSwitch);
246
48.4k
    if (instr->GetOperand(0).words.size() != 1) {
247
      // If the selector is wider than 32-bits, return varying. TODO(dnovillo):
248
      // Add support for wider constants.
249
0
      return SSAPropagator::kVarying;
250
0
    }
251
48.4k
    uint32_t select_id = instr->GetSingleWordOperand(0);
252
48.4k
    auto it = values_.find(select_id);
253
48.4k
    if (it == values_.end() || IsVaryingValue(it->second)) {
254
      // The selector has an unknown value, any of the branches could be taken.
255
2.36k
      return SSAPropagator::kVarying;
256
2.36k
    }
257
258
    // Get the constant value for the selector from the value table. Use it to
259
    // decide which branch will be taken.
260
46.0k
    uint32_t select_val_id = it->second;
261
46.0k
    const analysis::Constant* c =
262
46.0k
        const_mgr_->FindDeclaredConstant(select_val_id);
263
46.0k
    assert(c && "Expected to find a constant declaration for a known value.");
264
    // TODO: support 64-bit integer switches.
265
46.0k
    uint32_t constant_cond = 0;
266
46.0k
    if (const analysis::IntConstant* val = c->AsIntConstant()) {
267
45.9k
      constant_cond = val->words()[0];
268
45.9k
    } else {
269
      // Undef values should have returned varying above.
270
70
      assert(c->AsNullConstant());
271
70
      constant_cond = 0;
272
70
    }
273
274
    // Start assuming that the selector will take the default value;
275
46.0k
    dest_label = instr->GetSingleWordOperand(1);
276
50.2k
    for (uint32_t i = 2; i < instr->NumOperands(); i += 2) {
277
4.60k
      if (constant_cond == instr->GetSingleWordOperand(i)) {
278
428
        dest_label = instr->GetSingleWordOperand(i + 1);
279
428
        break;
280
428
      }
281
4.60k
    }
282
46.0k
  }
283
284
645k
  assert(dest_label && "Destination label should be set at this point.");
285
543k
  *dest_bb = context()->cfg()->block(dest_label);
286
543k
  return SSAPropagator::kInteresting;
287
543k
}
288
289
SSAPropagator::PropStatus CCPPass::VisitInstruction(Instruction* instr,
290
2.98M
                                                    BasicBlock** dest_bb) {
291
2.98M
  *dest_bb = nullptr;
292
2.98M
  if (instr->opcode() == spv::Op::OpPhi) {
293
404k
    return VisitPhi(instr);
294
2.58M
  } else if (instr->IsBranch()) {
295
645k
    return VisitBranch(instr, dest_bb);
296
1.93M
  } else if (instr->result_id()) {
297
1.51M
    return VisitAssignment(instr);
298
1.51M
  }
299
416k
  return SSAPropagator::kVarying;
300
2.98M
}
301
302
15.6k
bool CCPPass::ReplaceValues() {
303
  // Even if we make no changes to the function's IR, propagation may have
304
  // created new constants.  Even if those constants cannot be replaced in
305
  // the IR, the constant definition itself is a change.  To reflect this,
306
  // we check whether the next ID to be given by the module is different than
307
  // the original bound ID. If that happens, new instructions were added to the
308
  // module during propagation.
309
  //
310
  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/3636 and
311
  // https://github.com/KhronosGroup/SPIRV-Tools/issues/3991 for details.
312
15.6k
  bool changed_ir = (context()->module()->IdBound() > original_id_bound_);
313
314
2.33M
  for (const auto& it : values_) {
315
2.33M
    uint32_t id = it.first;
316
2.33M
    uint32_t cst_id = it.second;
317
2.33M
    if (!IsVaryingValue(cst_id) && id != cst_id) {
318
124k
      context()->KillNamesAndDecorates(id);
319
124k
      changed_ir |= context()->ReplaceAllUsesWith(id, cst_id);
320
124k
    }
321
2.33M
  }
322
323
15.6k
  return changed_ir;
324
15.6k
}
325
326
19.9k
bool CCPPass::PropagateConstants(Function* fp) {
327
19.9k
  if (fp->IsDeclaration()) {
328
0
    return false;
329
0
  }
330
331
  // Mark function parameters as varying.
332
19.9k
  fp->ForEachParam([this](const Instruction* inst) {
333
4.47k
    values_[inst->result_id()] = kVaryingSSAId;
334
4.47k
  });
335
336
2.98M
  const auto visit_fn = [this](Instruction* instr, BasicBlock** dest_bb) {
337
2.98M
    return VisitInstruction(instr, dest_bb);
338
2.98M
  };
339
340
19.9k
  propagator_ =
341
19.9k
      std::unique_ptr<SSAPropagator>(new SSAPropagator(context(), visit_fn));
342
343
19.9k
  if (propagator_->Run(fp)) {
344
15.6k
    return ReplaceValues();
345
15.6k
  }
346
347
4.28k
  return false;
348
19.9k
}
349
350
21.3k
void CCPPass::Initialize() {
351
21.3k
  const_mgr_ = context()->get_constant_mgr();
352
353
  // Populate the constant table with values from constant declarations in the
354
  // module.  The values of each OpConstant declaration is the identity
355
  // assignment (i.e., each constant is its own value).
356
454k
  for (const auto& inst : get_module()->types_values()) {
357
    // Record compile time constant ids. Treat all other global values as
358
    // varying.
359
454k
    if (inst.IsConstant()) {
360
186k
      values_[inst.result_id()] = inst.result_id();
361
268k
    } else {
362
268k
      values_[inst.result_id()] = kVaryingSSAId;
363
268k
    }
364
454k
  }
365
366
  // Mark the extended instruction imports as `kVarying`. We know they
367
  // will not be constants, and will be used by `OpExtInst` instructions.
368
  // This allows those instructions to be fully processed.
369
21.3k
  for (const auto& inst : get_module()->ext_inst_imports()) {
370
10.2k
    values_[inst.result_id()] = kVaryingSSAId;
371
10.2k
  }
372
373
21.3k
  original_id_bound_ = context()->module()->IdBound();
374
21.3k
}
375
376
21.3k
Pass::Status CCPPass::Process() {
377
21.3k
  Initialize();
378
379
  // Process all entry point functions.
380
21.3k
  ProcessFunction pfn = [this](Function* fp) { return PropagateConstants(fp); };
381
21.3k
  bool modified = context()->ProcessReachableCallTree(pfn);
382
21.3k
  if (context()->id_overflow()) return Pass::Status::Failure;
383
21.3k
  return modified ? Pass::Status::SuccessWithChange
384
21.3k
                  : Pass::Status::SuccessWithoutChange;
385
21.3k
}
386
387
}  // namespace opt
388
}  // namespace spvtools