Coverage Report

Created: 2026-11-12 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/opt/ssa_rewrite_pass.cpp
Line
Count
Source
1
// Copyright (c) 2018 Google LLC.
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 the SSA rewriting algorithm proposed in
16
//
17
//      Simple and Efficient Construction of Static Single Assignment Form.
18
//      Braun M., Buchwald S., Hack S., Leißa R., Mallon C., Zwinkau A. (2013)
19
//      In: Jhala R., De Bosschere K. (eds)
20
//      Compiler Construction. CC 2013.
21
//      Lecture Notes in Computer Science, vol 7791.
22
//      Springer, Berlin, Heidelberg
23
//
24
//      https://link.springer.com/chapter/10.1007/978-3-642-37051-9_6
25
//
26
// In contrast to common eager algorithms based on dominance and dominance
27
// frontier information, this algorithm works backwards from load operations.
28
//
29
// When a target variable is loaded, it queries the variable's reaching
30
// definition.  If the reaching definition is unknown at the current location,
31
// it searches backwards in the CFG, inserting Phi instructions at join points
32
// in the CFG along the way until it finds the desired store instruction.
33
//
34
// The algorithm avoids repeated lookups using memoization.
35
//
36
// For reducible CFGs, which are a superset of the structured CFGs in SPIRV,
37
// this algorithm is proven to produce minimal SSA.  That is, it inserts the
38
// minimal number of Phi instructions required to ensure the SSA property, but
39
// some Phi instructions may be dead
40
// (https://en.wikipedia.org/wiki/Static_single_assignment_form).
41
42
#include "source/opt/ssa_rewrite_pass.h"
43
44
#include <memory>
45
#include <sstream>
46
47
#include "source/opcode.h"
48
#include "source/opt/cfg.h"
49
#include "source/opt/mem_pass.h"
50
#include "source/opt/types.h"
51
52
// Debug logging (0: Off, 1-N: Verbosity level).  Replace this with the
53
// implementation done for
54
// https://github.com/KhronosGroup/SPIRV-Tools/issues/1351
55
// #define SSA_REWRITE_DEBUGGING_LEVEL 3
56
57
#ifdef SSA_REWRITE_DEBUGGING_LEVEL
58
#include <ostream>
59
#else
60
#define SSA_REWRITE_DEBUGGING_LEVEL 0
61
#endif
62
63
namespace spvtools {
64
namespace opt {
65
namespace {
66
constexpr uint32_t kStoreValIdInIdx = 1;
67
constexpr uint32_t kVariableInitIdInIdx = 1;
68
}  // namespace
69
70
0
std::string SSARewriter::PhiCandidate::PrettyPrint(const CFG* cfg) const {
71
0
  std::ostringstream str;
72
0
  str << "%" << result_id_ << " = Phi[%" << var_id_ << ", BB %" << bb_->id()
73
0
      << "](";
74
0
  if (phi_args_.size() > 0) {
75
0
    uint32_t arg_ix = 0;
76
0
    for (uint32_t pred_label : cfg->preds(bb_->id())) {
77
0
      uint32_t arg_id = phi_args_[arg_ix++];
78
0
      str << "[%" << arg_id << ", bb(%" << pred_label << ")] ";
79
0
    }
80
0
  }
81
0
  str << ")";
82
0
  if (copy_of_ != 0) {
83
0
    str << "  [COPY OF " << copy_of_ << "]";
84
0
  }
85
0
  str << ((is_complete_) ? "  [COMPLETE]" : "  [INCOMPLETE]");
86
87
0
  return str.str();
88
0
}
89
90
SSARewriter::PhiCandidate* SSARewriter::CreatePhiCandidate(uint32_t var_id,
91
1.12M
                                                           BasicBlock* bb) {
92
1.12M
  uint32_t phi_result_id = pass_->context()->TakeNextId();
93
1.12M
  if (phi_result_id == 0) {
94
0
    return nullptr;
95
0
  }
96
1.12M
  auto result = phi_candidates_.emplace(
97
1.12M
      phi_result_id, PhiCandidate(var_id, phi_result_id, bb));
98
1.12M
  PhiCandidate* phi_candidate = &result.first->second;
99
1.12M
  return phi_candidate;
100
1.12M
}
101
102
void SSARewriter::ReplacePhiUsersWith(const PhiCandidate& phi_to_remove,
103
952k
                                      uint32_t repl_id) {
104
4.06M
  for (uint32_t user_id : phi_to_remove.users()) {
105
4.06M
    PhiCandidate* user_phi = GetPhiCandidate(user_id);
106
4.06M
    BasicBlock* bb = pass_->context()->get_instr_block(user_id);
107
4.06M
    if (user_phi) {
108
      // If the user is a Phi candidate, replace all arguments that refer to
109
      // |phi_to_remove.result_id()| with |repl_id|.
110
2.17M
      for (uint32_t& arg : user_phi->phi_args()) {
111
2.17M
        if (arg == phi_to_remove.result_id()) {
112
756k
          arg = repl_id;
113
756k
        }
114
2.17M
      }
115
3.30M
    } else if (bb->id() == user_id) {
116
      // The phi candidate is the definition of the variable at basic block
117
      // |bb|.  We must change this to the replacement.
118
3.27M
      WriteVariable(phi_to_remove.var_id(), bb, repl_id);
119
3.27M
    } else {
120
      // For regular loads, traverse the |load_replacement_| table looking for
121
      // instances of |phi_to_remove|.
122
6.97M
      for (auto& it : load_replacement_) {
123
6.97M
        if (it.second == phi_to_remove.result_id()) {
124
39.9k
          it.second = repl_id;
125
39.9k
        }
126
6.97M
      }
127
39.9k
    }
128
4.06M
  }
129
952k
}
130
131
1.12M
uint32_t SSARewriter::TryRemoveTrivialPhi(PhiCandidate* phi_candidate) {
132
1.12M
  uint32_t same_id = 0;
133
2.60M
  for (uint32_t arg_id : phi_candidate->phi_args()) {
134
2.60M
    if (arg_id == same_id || arg_id == phi_candidate->result_id()) {
135
      // This is a self-reference operand or a reference to the same value ID.
136
1.31M
      continue;
137
1.31M
    }
138
1.29M
    if (same_id != 0) {
139
      // This Phi candidate merges at least two values.  Therefore, it is not
140
      // trivial.
141
170k
      assert(phi_candidate->copy_of() == 0 &&
142
170k
             "Phi candidate transitioning from copy to non-copy.");
143
170k
      return phi_candidate->result_id();
144
170k
    }
145
1.12M
    same_id = arg_id;
146
1.12M
  }
147
148
  // The previous logic has determined that this Phi candidate |phi_candidate|
149
  // is trivial.  It is essentially the copy operation phi_candidate->phi_result
150
  // = Phi(same, same, same, ...).  Since it is not necessary, we can re-route
151
  // all the users of |phi_candidate->phi_result| to all its users, and remove
152
  // |phi_candidate|.
153
154
  // Mark the Phi candidate as a trivial copy of |same_id|, so it won't be
155
  // generated.
156
952k
  phi_candidate->MarkCopyOf(same_id);
157
158
952k
  assert(same_id != 0 && "Completed Phis cannot have %0 in their arguments");
159
160
  // Since |phi_candidate| always produces |same_id|, replace all the users of
161
  // |phi_candidate| with |same_id|.
162
952k
  ReplacePhiUsersWith(*phi_candidate, same_id);
163
164
952k
  return same_id;
165
952k
}
166
167
1.12M
uint32_t SSARewriter::AddPhiOperands(PhiCandidate* phi_candidate) {
168
1.12M
  assert(phi_candidate->phi_args().size() == 0 &&
169
1.12M
         "Phi candidate already has arguments");
170
171
1.12M
  bool found_0_arg = false;
172
2.66M
  for (uint32_t pred : pass_->cfg()->preds(phi_candidate->bb()->id())) {
173
2.66M
    BasicBlock* pred_bb = pass_->cfg()->block(pred);
174
175
    // If |pred_bb| is not sealed, use %0 to indicate that
176
    // |phi_candidate| needs to be completed after the whole CFG has
177
    // been processed.
178
    //
179
    // Note that we cannot call GetReachingDef() in these cases
180
    // because this would generate an empty Phi candidate in
181
    // |pred_bb|.  When |pred_bb| is later processed, a new definition
182
    // for |phi_candidate->var_id_| will be lost because
183
    // |phi_candidate| will still be reached by the empty Phi.
184
    //
185
    // Consider:
186
    //
187
    //       BB %23:
188
    //           %38 = Phi[%i](%int_0[%1], %39[%25])
189
    //
190
    //           ...
191
    //
192
    //       BB %25: [Starts unsealed]
193
    //       %39 = Phi[%i]()
194
    //       %34 = ...
195
    //       OpStore %i %34    -> Currdef(%i) at %25 is %34
196
    //       OpBranch %23
197
    //
198
    // When we first create the Phi in %38, we add an operandless Phi in
199
    // %39 to hold the unknown reaching def for %i.
200
    //
201
    // But then, when we go to complete %39 at the end.  The reaching def
202
    // for %i in %25's predecessor is %38 itself.  So we miss the fact
203
    // that %25 has a def for %i that should be used.
204
    //
205
    // By making the argument %0, we make |phi_candidate| incomplete,
206
    // which will cause it to be completed after the whole CFG has
207
    // been scanned.
208
2.66M
    uint32_t arg_id = IsBlockSealed(pred_bb)
209
2.66M
                          ? GetReachingDef(phi_candidate->var_id(), pred_bb)
210
2.66M
                          : 0;
211
2.66M
    phi_candidate->phi_args().push_back(arg_id);
212
213
2.66M
    if (arg_id == 0) {
214
302k
      found_0_arg = true;
215
2.36M
    } else {
216
      // If this argument is another Phi candidate, add |phi_candidate| to the
217
      // list of users for the defining Phi.
218
2.36M
      PhiCandidate* defining_phi = GetPhiCandidate(arg_id);
219
2.36M
      if (defining_phi && defining_phi != phi_candidate) {
220
1.60M
        defining_phi->AddUser(phi_candidate->result_id());
221
1.60M
      }
222
2.36M
    }
223
2.66M
  }
224
225
  // If we could not fill-in all the arguments of this Phi, mark it incomplete
226
  // so it gets completed after the whole CFG has been processed.
227
1.12M
  if (found_0_arg) {
228
194k
    phi_candidate->MarkIncomplete();
229
194k
    incomplete_phis_.push(phi_candidate);
230
194k
    return phi_candidate->result_id();
231
194k
  }
232
233
  // Try to remove |phi_candidate|, if it's trivial.
234
927k
  uint32_t repl_id = TryRemoveTrivialPhi(phi_candidate);
235
927k
  if (repl_id == phi_candidate->result_id()) {
236
    // |phi_candidate| is complete and not trivial.  Add it to the
237
    // list of Phi candidates to generate.
238
103k
    phi_candidate->MarkComplete();
239
103k
    phis_to_generate_.push_back(phi_candidate);
240
103k
  }
241
242
927k
  return repl_id;
243
1.12M
}
244
245
5.93M
uint32_t SSARewriter::GetValueAtBlock(uint32_t var_id, BasicBlock* bb) {
246
5.93M
  assert(bb != nullptr);
247
5.93M
  const auto& bb_it = defs_at_block_.find(bb);
248
5.93M
  if (bb_it != defs_at_block_.end()) {
249
5.66M
    const auto& current_defs = bb_it->second;
250
5.66M
    const auto& var_it = current_defs.find(var_id);
251
5.66M
    if (var_it != current_defs.end()) {
252
1.54M
      return var_it->second;
253
1.54M
    }
254
5.66M
  }
255
4.39M
  return 0;
256
5.93M
}
257
258
5.93M
uint32_t SSARewriter::GetReachingDef(uint32_t var_id, BasicBlock* bb) {
259
  // If |var_id| has a definition in |bb|, return it.
260
5.93M
  uint32_t val_id = GetValueAtBlock(var_id, bb);
261
5.93M
  if (val_id != 0) return val_id;
262
263
  // Otherwise, look up the value for |var_id| in |bb|'s predecessors.
264
4.39M
  auto& predecessors = pass_->cfg()->preds(bb->id());
265
4.39M
  if (predecessors.size() == 1) {
266
    // If |bb| has exactly one predecessor, we look for |var_id|'s definition
267
    // there.
268
3.21M
    val_id = GetReachingDef(var_id, pass_->cfg()->block(predecessors[0]));
269
3.21M
  } else if (predecessors.size() > 1) {
270
    // If there is more than one predecessor, this is a join block which may
271
    // require a Phi instruction.  This will act as |var_id|'s current
272
    // definition to break potential cycles.
273
1.12M
    PhiCandidate* phi_candidate = CreatePhiCandidate(var_id, bb);
274
1.12M
    if (phi_candidate == nullptr) return 0;
275
276
    // Set the value for |bb| to avoid an infinite recursion.
277
1.12M
    WriteVariable(var_id, bb, phi_candidate->result_id());
278
1.12M
    val_id = AddPhiOperands(phi_candidate);
279
1.12M
  }
280
281
  // If we could not find a store for this variable in the path from the root
282
  // of the CFG, the variable is not defined, so we use undef.
283
4.39M
  if (val_id == 0) {
284
51.1k
    val_id = pass_->GetUndefVal(var_id);
285
51.1k
    if (val_id == 0) {
286
0
      return 0;
287
0
    }
288
51.1k
  }
289
290
4.39M
  WriteVariable(var_id, bb, val_id);
291
292
4.39M
  return val_id;
293
4.39M
}
294
295
574k
void SSARewriter::SealBlock(BasicBlock* bb) {
296
574k
  auto result = sealed_blocks_.insert(bb);
297
574k
  (void)result;
298
574k
  assert(result.second == true &&
299
574k
         "Tried to seal the same basic block more than once.");
300
574k
}
301
302
641k
void SSARewriter::ProcessStore(Instruction* inst, BasicBlock* bb) {
303
641k
  auto opcode = inst->opcode();
304
641k
  assert((opcode == spv::Op::OpStore || opcode == spv::Op::OpVariable) &&
305
641k
         "Expecting a store or a variable definition instruction.");
306
307
641k
  uint32_t var_id = 0;
308
641k
  uint32_t val_id = 0;
309
641k
  if (opcode == spv::Op::OpStore) {
310
448k
    (void)pass_->GetPtr(inst, &var_id);
311
448k
    val_id = inst->GetSingleWordInOperand(kStoreValIdInIdx);
312
448k
  } else if (inst->NumInOperands() >= 2) {
313
13.5k
    var_id = inst->result_id();
314
13.5k
    val_id = inst->GetSingleWordInOperand(kVariableInitIdInIdx);
315
13.5k
  }
316
641k
  if (pass_->IsTargetVar(var_id)) {
317
253k
    WriteVariable(var_id, bb, val_id);
318
253k
    pass_->context()->get_debug_info_mgr()->AddDebugValueForVariable(
319
253k
        inst, var_id, val_id, inst);
320
321
#if SSA_REWRITE_DEBUGGING_LEVEL > 1
322
    std::cerr << "\tFound store '%" << var_id << " = %" << val_id << "': "
323
              << inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)
324
              << "\n";
325
#endif
326
253k
  }
327
641k
}
328
329
478k
bool SSARewriter::ProcessLoad(Instruction* inst, BasicBlock* bb) {
330
  // Get the pointer that we are using to load from.
331
478k
  uint32_t var_id = 0;
332
478k
  (void)pass_->GetPtr(inst, &var_id);
333
334
  // Get the immediate reaching definition for |var_id|.
335
  //
336
  // In the presence of variable pointers, the reaching definition may be
337
  // another pointer.  For example, the following fragment:
338
  //
339
  //  %2 = OpVariable %_ptr_Input_float Input
340
  // %11 = OpVariable %_ptr_Function__ptr_Input_float Function
341
  //       OpStore %11 %2
342
  // %12 = OpLoad %_ptr_Input_float %11
343
  // %13 = OpLoad %float %12
344
  //
345
  // corresponds to the pseudo-code:
346
  //
347
  // layout(location = 0) in flat float *%2
348
  // float %13;
349
  // float *%12;
350
  // float **%11;
351
  // *%11 = %2;
352
  // %12 = *%11;
353
  // %13 = *%12;
354
  //
355
  // which ultimately, should correspond to:
356
  //
357
  // %13 = *%2;
358
  //
359
  // During rewriting, the pointer %12 is found to be replaceable by %2 (i.e.,
360
  // load_replacement_[12] is 2). However, when processing the load
361
  // %13 = *%12, the type of %12's reaching definition is another float
362
  // pointer (%2), instead of a float value.
363
  //
364
  // When this happens, we need to continue looking up the reaching definition
365
  // chain until we get to a float value or a non-target var (i.e. a variable
366
  // that cannot be SSA replaced, like %2 in this case since it is a function
367
  // argument).
368
478k
  analysis::DefUseManager* def_use_mgr = pass_->context()->get_def_use_mgr();
369
478k
  analysis::TypeManager* type_mgr = pass_->context()->get_type_mgr();
370
478k
  analysis::Type* load_type = type_mgr->GetType(inst->type_id());
371
478k
  uint32_t val_id = 0;
372
478k
  bool found_reaching_def = false;
373
741k
  while (!found_reaching_def) {
374
478k
    if (!pass_->IsTargetVar(var_id)) {
375
      // If the variable we are loading from is not an SSA target (globals,
376
      // function parameters), do nothing.
377
216k
      return true;
378
216k
    }
379
380
262k
    val_id = GetReachingDef(var_id, bb);
381
262k
    if (val_id == 0) {
382
0
      return false;
383
0
    }
384
385
    // If the reaching definition is a pointer type different than the type of
386
    // the instruction we are analyzing, then it must be a reference to another
387
    // pointer (otherwise, this would be invalid SPIRV).  We continue
388
    // de-referencing it by making |val_id| be |var_id|.
389
    //
390
    // NOTE: if there is no reaching definition instruction, it means |val_id|
391
    // is an undef.
392
262k
    Instruction* reaching_def_inst = def_use_mgr->GetDef(val_id);
393
262k
    if (reaching_def_inst &&
394
68.0k
        !type_mgr->GetType(reaching_def_inst->type_id())->IsSame(load_type)) {
395
0
      var_id = val_id;
396
262k
    } else {
397
262k
      found_reaching_def = true;
398
262k
    }
399
262k
  }
400
401
  // Schedule a replacement for the result of this load instruction with
402
  // |val_id|. After all the rewriting decisions are made, every use of
403
  // this load will be replaced with |val_id|.
404
262k
  uint32_t load_id = inst->result_id();
405
262k
  assert(load_replacement_.count(load_id) == 0);
406
262k
  load_replacement_[load_id] = val_id;
407
262k
  PhiCandidate* defining_phi = GetPhiCandidate(val_id);
408
262k
  if (defining_phi) {
409
194k
    defining_phi->AddUser(load_id);
410
194k
  }
411
412
#if SSA_REWRITE_DEBUGGING_LEVEL > 1
413
  std::cerr << "\tFound load: "
414
            << inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)
415
            << " (replacement for %" << load_id << " is %" << val_id << ")\n";
416
#endif
417
418
262k
  return true;
419
262k
}
420
421
0
void SSARewriter::PrintPhiCandidates() const {
422
0
  std::cerr << "\nPhi candidates:\n";
423
0
  for (const auto& phi_it : phi_candidates_) {
424
0
    std::cerr << "\tBB %" << phi_it.second.bb()->id() << ": "
425
0
              << phi_it.second.PrettyPrint(pass_->cfg()) << "\n";
426
0
  }
427
0
  std::cerr << "\n";
428
0
}
429
430
0
void SSARewriter::PrintReplacementTable() const {
431
0
  std::cerr << "\nLoad replacement table\n";
432
0
  for (const auto& it : load_replacement_) {
433
0
    std::cerr << "\t%" << it.first << " -> %" << it.second << "\n";
434
0
  }
435
0
  std::cerr << "\n";
436
0
}
437
438
574k
bool SSARewriter::GenerateSSAReplacements(BasicBlock* bb) {
439
#if SSA_REWRITE_DEBUGGING_LEVEL > 1
440
  std::cerr << "Generating SSA replacements for block: " << bb->id() << "\n";
441
  std::cerr << bb->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)
442
            << "\n";
443
#endif
444
445
3.08M
  for (auto& inst : *bb) {
446
3.08M
    auto opcode = inst.opcode();
447
3.08M
    if (opcode == spv::Op::OpStore || opcode == spv::Op::OpVariable) {
448
641k
      ProcessStore(&inst, bb);
449
2.44M
    } else if (inst.opcode() == spv::Op::OpLoad) {
450
478k
      if (!ProcessLoad(&inst, bb)) {
451
0
        return false;
452
0
      }
453
478k
    }
454
3.08M
  }
455
456
  // Seal |bb|. This means that all the stores in it have been scanned and
457
  // it's ready to feed them into its successors.
458
574k
  SealBlock(bb);
459
460
#if SSA_REWRITE_DEBUGGING_LEVEL > 1
461
  PrintPhiCandidates();
462
  PrintReplacementTable();
463
  std::cerr << "\n\n";
464
#endif
465
574k
  return true;
466
574k
}
467
468
262k
uint32_t SSARewriter::GetReplacement(std::pair<uint32_t, uint32_t> repl) {
469
262k
  uint32_t val_id = repl.second;
470
262k
  auto it = load_replacement_.find(val_id);
471
286k
  while (it != load_replacement_.end()) {
472
23.9k
    val_id = it->second;
473
23.9k
    it = load_replacement_.find(val_id);
474
23.9k
  }
475
262k
  return val_id;
476
262k
}
477
478
uint32_t SSARewriter::GetPhiArgument(const PhiCandidate* phi_candidate,
479
427k
                                     uint32_t ix) {
480
427k
  assert(phi_candidate->IsReady() &&
481
427k
         "Tried to get the final argument from an incomplete/trivial Phi");
482
483
427k
  uint32_t arg_id = phi_candidate->phi_args()[ix];
484
427k
  while (arg_id != 0) {
485
427k
    PhiCandidate* phi_user = GetPhiCandidate(arg_id);
486
427k
    if (phi_user == nullptr || phi_user->IsReady()) {
487
      // If the argument is not a Phi or it's a Phi candidate ready to be
488
      // emitted, return it.
489
427k
      return arg_id;
490
427k
    }
491
531
    arg_id = phi_user->copy_of();
492
531
  }
493
494
0
  if (pass_->context()->id_overflow()) {
495
0
    return 0;
496
0
  }
497
498
0
  assert(false &&
499
0
         "No Phi candidates in the copy-of chain are ready to be generated");
500
501
0
  return 0;
502
0
}
503
504
10.7k
bool SSARewriter::ApplyReplacements() {
505
10.7k
  bool modified = false;
506
507
#if SSA_REWRITE_DEBUGGING_LEVEL > 2
508
  std::cerr << "\n\nApplying replacement decisions to IR\n\n";
509
  PrintPhiCandidates();
510
  PrintReplacementTable();
511
  std::cerr << "\n\n";
512
#endif
513
514
  // Add Phi instructions from completed Phi candidates.
515
10.7k
  std::vector<Instruction*> generated_phis;
516
170k
  for (const PhiCandidate* phi_candidate : phis_to_generate_) {
517
#if SSA_REWRITE_DEBUGGING_LEVEL > 2
518
    std::cerr << "Phi candidate: " << phi_candidate->PrettyPrint(pass_->cfg())
519
              << "\n";
520
#endif
521
522
170k
    assert(phi_candidate->is_complete() &&
523
170k
           "Tried to instantiate a Phi instruction from an incomplete Phi "
524
170k
           "candidate");
525
526
170k
    auto* local_var = pass_->get_def_use_mgr()->GetDef(phi_candidate->var_id());
527
528
    // Build the vector of operands for the new OpPhi instruction.
529
170k
    uint32_t type_id = pass_->GetPointeeTypeId(local_var);
530
170k
    std::vector<Operand> phi_operands;
531
170k
    uint32_t arg_ix = 0;
532
170k
    std::unordered_map<uint32_t, uint32_t> already_seen;
533
427k
    for (uint32_t pred_label : pass_->cfg()->preds(phi_candidate->bb()->id())) {
534
427k
      uint32_t op_val_id = GetPhiArgument(phi_candidate, arg_ix++);
535
427k
      if (already_seen.count(pred_label) == 0) {
536
426k
        phi_operands.push_back(
537
426k
            {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {op_val_id}});
538
426k
        phi_operands.push_back(
539
426k
            {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {pred_label}});
540
426k
        already_seen[pred_label] = op_val_id;
541
426k
      } else {
542
        // It is possible that there are two edges from the same parent block.
543
        // Since the OpPhi can have only one entry for each parent, we have to
544
        // make sure the two edges are consistent with each other.
545
912
        assert(already_seen[pred_label] == op_val_id &&
546
912
               "Inconsistent value for duplicate edges.");
547
912
      }
548
427k
    }
549
550
    // Generate a new OpPhi instruction and insert it in its basic
551
    // block.
552
170k
    std::unique_ptr<Instruction> phi_inst(
553
170k
        new Instruction(pass_->context(), spv::Op::OpPhi, type_id,
554
170k
                        phi_candidate->result_id(), phi_operands));
555
170k
    generated_phis.push_back(phi_inst.get());
556
170k
    pass_->get_def_use_mgr()->AnalyzeInstDef(&*phi_inst);
557
170k
    pass_->context()->set_instr_block(&*phi_inst, phi_candidate->bb());
558
170k
    auto insert_it = phi_candidate->bb()->begin();
559
170k
    insert_it = insert_it.InsertBefore(std::move(phi_inst));
560
170k
    pass_->context()->get_decoration_mgr()->CloneDecorations(
561
170k
        phi_candidate->var_id(), phi_candidate->result_id(),
562
170k
        {spv::Decoration::RelaxedPrecision});
563
564
    // Add DebugValue for the new OpPhi instruction.
565
170k
    insert_it->SetDebugScope(local_var->GetDebugScope());
566
170k
    pass_->context()->get_debug_info_mgr()->AddDebugValueForVariable(
567
170k
        &*insert_it, phi_candidate->var_id(), phi_candidate->result_id(),
568
170k
        &*insert_it);
569
570
170k
    modified = true;
571
170k
  }
572
573
  // Scan uses for all inserted Phi instructions. Do this separately from the
574
  // registration of the Phi instruction itself to avoid trying to analyze
575
  // uses of Phi instructions that have not been registered yet.
576
170k
  for (Instruction* phi_inst : generated_phis) {
577
170k
    pass_->get_def_use_mgr()->AnalyzeInstUse(&*phi_inst);
578
170k
  }
579
580
#if SSA_REWRITE_DEBUGGING_LEVEL > 1
581
  std::cerr << "\n\nReplacing the result of load instructions with the "
582
               "corresponding SSA id\n\n";
583
#endif
584
585
  // Apply replacements from the load replacement table.
586
262k
  for (auto& repl : load_replacement_) {
587
262k
    uint32_t load_id = repl.first;
588
262k
    uint32_t val_id = GetReplacement(repl);
589
262k
    Instruction* load_inst =
590
262k
        pass_->context()->get_def_use_mgr()->GetDef(load_id);
591
592
#if SSA_REWRITE_DEBUGGING_LEVEL > 2
593
    std::cerr << "\t"
594
              << load_inst->PrettyPrint(
595
                     SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)
596
              << "  (%" << load_id << " -> %" << val_id << ")\n";
597
#endif
598
599
    // Remove the load instruction and replace all the uses of this load's
600
    // result with |val_id|.  Kill any names or decorates using the load's
601
    // result before replacing to prevent incorrect replacement in those
602
    // instructions.
603
262k
    pass_->context()->KillNamesAndDecorates(load_id);
604
262k
    pass_->context()->ReplaceAllUsesWith(load_id, val_id);
605
262k
    pass_->context()->KillInst(load_inst);
606
262k
    modified = true;
607
262k
  }
608
609
10.7k
  return modified;
610
10.7k
}
611
612
194k
void SSARewriter::FinalizePhiCandidate(PhiCandidate* phi_candidate) {
613
194k
  assert(phi_candidate->phi_args().size() > 0 &&
614
194k
         "Phi candidate should have arguments");
615
616
194k
  uint32_t ix = 0;
617
634k
  for (uint32_t pred : pass_->cfg()->preds(phi_candidate->bb()->id())) {
618
634k
    BasicBlock* pred_bb = pass_->cfg()->block(pred);
619
634k
    uint32_t& arg_id = phi_candidate->phi_args()[ix++];
620
634k
    if (arg_id == 0) {
621
      // If |pred_bb| is still not sealed, it means it's unreachable. In this
622
      // case, we just use Undef as an argument.
623
302k
      arg_id = IsBlockSealed(pred_bb)
624
302k
                   ? GetReachingDef(phi_candidate->var_id(), pred_bb)
625
302k
                   : pass_->GetUndefVal(phi_candidate->var_id());
626
302k
    }
627
634k
  }
628
629
  // This candidate is now completed.
630
194k
  phi_candidate->MarkComplete();
631
632
  // If |phi_candidate| is not trivial, add it to the list of Phis to
633
  // generate.
634
194k
  if (TryRemoveTrivialPhi(phi_candidate) == phi_candidate->result_id()) {
635
    // If we could not remove |phi_candidate|, it means that it is complete
636
    // and not trivial. Add it to the list of Phis to generate.
637
66.8k
    assert(!phi_candidate->copy_of() && "A completed Phi cannot be trivial.");
638
66.8k
    phis_to_generate_.push_back(phi_candidate);
639
66.8k
  }
640
194k
}
641
642
10.7k
void SSARewriter::FinalizePhiCandidates() {
643
#if SSA_REWRITE_DEBUGGING_LEVEL > 1
644
  std::cerr << "Finalizing Phi candidates:\n\n";
645
  PrintPhiCandidates();
646
  std::cerr << "\n";
647
#endif
648
649
  // Now, complete the collected candidates.
650
205k
  while (incomplete_phis_.size() > 0) {
651
194k
    PhiCandidate* phi_candidate = incomplete_phis_.front();
652
194k
    incomplete_phis_.pop();
653
194k
    FinalizePhiCandidate(phi_candidate);
654
194k
  }
655
10.7k
}
656
657
10.7k
Pass::Status SSARewriter::RewriteFunctionIntoSSA(Function* fp) {
658
#if SSA_REWRITE_DEBUGGING_LEVEL > 0
659
  std::cerr << "Function before SSA rewrite:\n"
660
            << fp->PrettyPrint(0) << "\n\n\n";
661
#endif
662
663
  // Collect variables that can be converted into SSA IDs.
664
10.7k
  pass_->CollectTargetVars(fp);
665
666
  // Generate all the SSA replacements and Phi candidates. This will
667
  // generate incomplete and trivial Phis.
668
10.7k
  bool succeeded = pass_->cfg()->WhileEachBlockInReversePostOrder(
669
574k
      fp->entry().get(), [this](BasicBlock* bb) {
670
574k
        if (!GenerateSSAReplacements(bb)) {
671
0
          return false;
672
0
        }
673
574k
        return true;
674
574k
      });
675
676
10.7k
  if (!succeeded || pass_->context()->id_overflow()) {
677
0
    return Pass::Status::Failure;
678
0
  }
679
680
  // Remove trivial Phis and add arguments to incomplete Phis.
681
10.7k
  FinalizePhiCandidates();
682
683
10.7k
  if (pass_->context()->id_overflow()) {
684
0
    return Pass::Status::Failure;
685
0
  }
686
687
  // Finally, apply all the replacements in the IR.
688
10.7k
  bool modified = ApplyReplacements();
689
690
10.7k
  if (pass_->context()->id_overflow()) {
691
0
    return Pass::Status::Failure;
692
0
  }
693
694
#if SSA_REWRITE_DEBUGGING_LEVEL > 0
695
  std::cerr << "\n\n\nFunction after SSA rewrite:\n"
696
            << fp->PrettyPrint(0) << "\n";
697
#endif
698
699
10.7k
  return modified ? Pass::Status::SuccessWithChange
700
10.7k
                  : Pass::Status::SuccessWithoutChange;
701
10.7k
}
702
703
9.06k
Pass::Status SSARewritePass::Process() {
704
9.06k
  Status status = Status::SuccessWithoutChange;
705
10.7k
  for (auto& fn : *get_module()) {
706
10.7k
    if (fn.IsDeclaration()) {
707
0
      continue;
708
0
    }
709
10.7k
    status =
710
10.7k
        CombineStatus(status, SSARewriter(this).RewriteFunctionIntoSSA(&fn));
711
10.7k
    if (status == Status::Failure || context()->id_overflow()) {
712
0
      return Status::Failure;
713
0
    }
714
    // Kill DebugDeclares for target variables.
715
143k
    for (auto var_id : seen_target_vars_) {
716
143k
      context()->get_debug_info_mgr()->KillDebugDeclares(var_id);
717
143k
    }
718
10.7k
  }
719
9.06k
  return status;
720
9.06k
}
721
722
}  // namespace opt
723
}  // namespace spvtools