Coverage Report

Created: 2025-12-31 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/opt/if_conversion.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
#include "source/opt/if_conversion.h"
16
17
#include <memory>
18
#include <vector>
19
20
#include "source/opt/value_number_table.h"
21
22
namespace spvtools {
23
namespace opt {
24
25
9.58k
Pass::Status IfConversion::Process() {
26
9.58k
  if (!context()->get_feature_mgr()->HasCapability(spv::Capability::Shader)) {
27
33
    return Status::SuccessWithoutChange;
28
33
  }
29
30
9.55k
  const ValueNumberTable& vn_table = *context()->GetValueNumberTable();
31
9.55k
  bool modified = false;
32
9.55k
  std::vector<Instruction*> to_kill;
33
9.55k
  for (auto& func : *get_module()) {
34
9.20k
    DominatorAnalysis* dominators = context()->GetDominatorAnalysis(&func);
35
765k
    for (auto& block : func) {
36
      // Check if it is possible for |block| to have phis that can be
37
      // transformed.
38
765k
      BasicBlock* common = nullptr;
39
765k
      if (!CheckBlock(&block, dominators, &common)) continue;
40
41
      // Get an insertion point.
42
28.1k
      auto iter = block.begin();
43
51.7k
      while (iter != block.end() && iter->opcode() == spv::Op::OpPhi) {
44
23.5k
        ++iter;
45
23.5k
      }
46
47
28.1k
      InstructionBuilder builder(
48
28.1k
          context(), &*iter,
49
28.1k
          IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
50
28.1k
      block.ForEachPhiInst([this, &builder, &modified, &common, &to_kill,
51
28.1k
                            dominators, &block, &vn_table](Instruction* phi) {
52
        // This phi is not compatible, but subsequent phis might be.
53
23.5k
        if (!CheckType(phi->type_id())) return;
54
55
        // We cannot transform cases where the phi is used by another phi in the
56
        // same block due to instruction ordering restrictions.
57
        // TODO(alan-baker): If all inappropriate uses could also be
58
        // transformed, we could still remove this phi.
59
23.5k
        if (!CheckPhiUsers(phi, &block)) return;
60
61
        // Identify the incoming values associated with the true and false
62
        // branches. If |then_block| dominates |inc0| or if the true edge
63
        // branches straight to this block and |common| is |inc0|, then |inc0|
64
        // is on the true branch. Otherwise the |inc1| is on the true branch.
65
23.5k
        BasicBlock* inc0 = GetIncomingBlock(phi, 0u);
66
23.5k
        Instruction* branch = common->terminator();
67
23.5k
        uint32_t condition = branch->GetSingleWordInOperand(0u);
68
23.5k
        BasicBlock* then_block = GetBlock(branch->GetSingleWordInOperand(1u));
69
23.5k
        Instruction* true_value = nullptr;
70
23.5k
        Instruction* false_value = nullptr;
71
23.5k
        if ((then_block == &block && inc0 == common) ||
72
23.2k
            dominators->Dominates(then_block, inc0)) {
73
5.58k
          true_value = GetIncomingValue(phi, 0u);
74
5.58k
          false_value = GetIncomingValue(phi, 1u);
75
17.9k
        } else {
76
17.9k
          true_value = GetIncomingValue(phi, 1u);
77
17.9k
          false_value = GetIncomingValue(phi, 0u);
78
17.9k
        }
79
80
23.5k
        BasicBlock* true_def_block = context()->get_instr_block(true_value);
81
23.5k
        BasicBlock* false_def_block = context()->get_instr_block(false_value);
82
83
23.5k
        uint32_t true_vn = vn_table.GetValueNumber(true_value);
84
23.5k
        uint32_t false_vn = vn_table.GetValueNumber(false_value);
85
23.5k
        if (true_vn != 0 && true_vn == false_vn) {
86
468
          Instruction* inst_to_use = nullptr;
87
88
          // Try to pick an instruction that is not in a side node.  If we can't
89
          // pick either the true for false branch as long as they can be
90
          // legally moved.
91
468
          if (!true_def_block ||
92
352
              dominators->Dominates(true_def_block, &block)) {
93
116
            inst_to_use = true_value;
94
352
          } else if (!false_def_block ||
95
352
                     dominators->Dominates(false_def_block, &block)) {
96
309
            inst_to_use = false_value;
97
309
          } else if (CanHoistInstruction(true_value, common, dominators)) {
98
35
            inst_to_use = true_value;
99
35
          } else if (CanHoistInstruction(false_value, common, dominators)) {
100
0
            inst_to_use = false_value;
101
0
          }
102
103
468
          if (inst_to_use != nullptr) {
104
460
            modified = true;
105
460
            HoistInstruction(inst_to_use, common, dominators);
106
460
            context()->KillNamesAndDecorates(phi);
107
460
            context()->ReplaceAllUsesWith(phi->result_id(),
108
460
                                          inst_to_use->result_id());
109
460
          }
110
468
          return;
111
468
        }
112
113
        // If either incoming value is defined in a block that does not dominate
114
        // this phi, then we cannot eliminate the phi with a select.
115
        // TODO(alan-baker): Perform code motion where it makes sense to enable
116
        // the transform in this case.
117
23.0k
        if (true_def_block && !dominators->Dominates(true_def_block, &block))
118
12.1k
          return;
119
120
10.9k
        if (false_def_block && !dominators->Dominates(false_def_block, &block))
121
1.16k
          return;
122
123
9.73k
        analysis::Type* data_ty =
124
9.73k
            context()->get_type_mgr()->GetType(true_value->type_id());
125
9.73k
        if (analysis::Vector* vec_data_ty = data_ty->AsVector()) {
126
1.22k
          condition = SplatCondition(vec_data_ty, condition, &builder);
127
1.22k
        }
128
129
        // TODO(1841): Handle id overflow.
130
9.73k
        Instruction* select = builder.AddSelect(phi->type_id(), condition,
131
9.73k
                                                true_value->result_id(),
132
9.73k
                                                false_value->result_id());
133
9.73k
        context()->get_def_use_mgr()->AnalyzeInstDefUse(select);
134
9.73k
        select->UpdateDebugInfoFrom(phi);
135
9.73k
        context()->ReplaceAllUsesWith(phi->result_id(), select->result_id());
136
9.73k
        to_kill.push_back(phi);
137
9.73k
        modified = true;
138
139
9.73k
        return;
140
10.9k
      });
141
28.1k
    }
142
9.20k
  }
143
144
9.73k
  for (auto inst : to_kill) {
145
9.73k
    context()->KillInst(inst);
146
9.73k
  }
147
148
9.55k
  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
149
9.58k
}
150
151
bool IfConversion::CheckBlock(BasicBlock* block, DominatorAnalysis* dominators,
152
765k
                              BasicBlock** common) {
153
765k
  const std::vector<uint32_t>& preds = cfg()->preds(block->id());
154
155
  // TODO(alan-baker): Extend to more than two predecessors
156
765k
  if (preds.size() != 2) return false;
157
158
69.6k
  BasicBlock* inc0 = context()->get_instr_block(preds[0]);
159
69.6k
  if (dominators->Dominates(block, inc0)) return false;
160
161
69.6k
  BasicBlock* inc1 = context()->get_instr_block(preds[1]);
162
69.6k
  if (dominators->Dominates(block, inc1)) return false;
163
164
56.7k
  if (inc0 == inc1) {
165
    // If the predecessor blocks are the same, then there is only 1 value for
166
    // the OpPhi.  Other transformation should be able to simplify that.
167
3.20k
    return false;
168
3.20k
  }
169
  // All phis will have the same common dominator, so cache the result
170
  // for this block. If there is no common dominator, then we cannot transform
171
  // any phi in this basic block.
172
53.5k
  *common = dominators->CommonDominator(inc0, inc1);
173
53.5k
  if (!*common || cfg()->IsPseudoEntryBlock(*common)) return false;
174
47.4k
  Instruction* branch = (*common)->terminator();
175
47.4k
  if (branch->opcode() != spv::Op::OpBranchConditional) return false;
176
47.0k
  auto merge = (*common)->GetMergeInst();
177
47.0k
  if (!merge || merge->opcode() != spv::Op::OpSelectionMerge) return false;
178
42.7k
  if (spv::SelectionControlMask(merge->GetSingleWordInOperand(1)) ==
179
42.7k
      spv::SelectionControlMask::DontFlatten) {
180
4.07k
    return false;
181
4.07k
  }
182
38.6k
  if ((*common)->MergeBlockIdIfAny() != block->id()) return false;
183
184
28.1k
  return true;
185
38.6k
}
186
187
23.5k
bool IfConversion::CheckPhiUsers(Instruction* phi, BasicBlock* block) {
188
23.5k
  return get_def_use_mgr()->WhileEachUser(
189
47.4k
      phi, [block, this](Instruction* user) {
190
47.4k
        if (user->opcode() == spv::Op::OpPhi &&
191
12.0k
            context()->get_instr_block(user) == block)
192
0
          return false;
193
47.4k
        return true;
194
47.4k
      });
195
23.5k
}
196
197
uint32_t IfConversion::SplatCondition(analysis::Vector* vec_data_ty,
198
                                      uint32_t cond,
199
1.22k
                                      InstructionBuilder* builder) {
200
  // If the data inputs to OpSelect are vectors, the condition for
201
  // OpSelect must be a boolean vector with the same number of
202
  // components. So splat the condition for the branch into a vector
203
  // type.
204
1.22k
  analysis::Bool bool_ty;
205
1.22k
  analysis::Vector bool_vec_ty(&bool_ty, vec_data_ty->element_count());
206
1.22k
  uint32_t bool_vec_id =
207
1.22k
      context()->get_type_mgr()->GetTypeInstruction(&bool_vec_ty);
208
1.22k
  std::vector<uint32_t> ids(vec_data_ty->element_count(), cond);
209
  // TODO(1841): Handle id overflow.
210
1.22k
  return builder->AddCompositeConstruct(bool_vec_id, ids)->result_id();
211
1.22k
}
212
213
23.5k
bool IfConversion::CheckType(uint32_t id) {
214
23.5k
  Instruction* type = get_def_use_mgr()->GetDef(id);
215
23.5k
  spv::Op op = type->opcode();
216
23.5k
  if (spvOpcodeIsScalarType(op) || op == spv::Op::OpTypePointer ||
217
8.75k
      op == spv::Op::OpTypeVector)
218
23.5k
    return true;
219
29
  return false;
220
23.5k
}
221
222
47.1k
BasicBlock* IfConversion::GetBlock(uint32_t id) {
223
47.1k
  return context()->get_instr_block(get_def_use_mgr()->GetDef(id));
224
47.1k
}
225
226
BasicBlock* IfConversion::GetIncomingBlock(Instruction* phi,
227
23.5k
                                           uint32_t predecessor) {
228
23.5k
  uint32_t in_index = 2 * predecessor + 1;
229
23.5k
  return GetBlock(phi->GetSingleWordInOperand(in_index));
230
23.5k
}
231
232
Instruction* IfConversion::GetIncomingValue(Instruction* phi,
233
47.1k
                                            uint32_t predecessor) {
234
47.1k
  uint32_t in_index = 2 * predecessor;
235
47.1k
  return get_def_use_mgr()->GetDef(phi->GetSingleWordInOperand(in_index));
236
47.1k
}
237
238
void IfConversion::HoistInstruction(Instruction* inst, BasicBlock* target_block,
239
534
                                    DominatorAnalysis* dominators) {
240
534
  BasicBlock* inst_block = context()->get_instr_block(inst);
241
534
  if (!inst_block) {
242
    // This is in the header, and dominates everything.
243
147
    return;
244
147
  }
245
246
387
  if (dominators->Dominates(inst_block, target_block)) {
247
    // Already in position.  No work to do.
248
350
    return;
249
350
  }
250
251
387
  assert(inst->IsOpcodeCodeMotionSafe() &&
252
37
         "Trying to move an instruction that is not safe to move.");
253
254
  // First hoist all instructions it depends on.
255
37
  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
256
37
  inst->ForEachInId(
257
74
      [this, target_block, def_use_mgr, dominators](uint32_t* id) {
258
74
        Instruction* operand_inst = def_use_mgr->GetDef(*id);
259
74
        HoistInstruction(operand_inst, target_block, dominators);
260
74
      });
261
262
37
  Instruction* insertion_pos = target_block->terminator();
263
37
  if ((insertion_pos)->PreviousNode()->opcode() == spv::Op::OpSelectionMerge) {
264
37
    insertion_pos = insertion_pos->PreviousNode();
265
37
  }
266
37
  inst->RemoveFromList();
267
37
  insertion_pos->InsertBefore(std::unique_ptr<Instruction>(inst));
268
37
  context()->set_instr_block(inst, target_block);
269
37
}
270
271
bool IfConversion::CanHoistInstruction(Instruction* inst,
272
                                       BasicBlock* target_block,
273
127
                                       DominatorAnalysis* dominators) {
274
127
  BasicBlock* inst_block = context()->get_instr_block(inst);
275
127
  if (!inst_block) {
276
    // This is in the header, and dominates everything.
277
31
    return true;
278
31
  }
279
280
96
  if (dominators->Dominates(inst_block, target_block)) {
281
    // Already in position.  No work to do.
282
42
    return true;
283
42
  }
284
285
54
  if (!inst->IsOpcodeCodeMotionSafe()) {
286
16
    return false;
287
16
  }
288
289
  // Check all instruction |inst| depends on.
290
38
  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
291
38
  return inst->WhileEachInId(
292
76
      [this, target_block, def_use_mgr, dominators](uint32_t* id) {
293
76
        Instruction* operand_inst = def_use_mgr->GetDef(*id);
294
76
        return CanHoistInstruction(operand_inst, target_block, dominators);
295
76
      });
296
54
}
297
298
}  // namespace opt
299
}  // namespace spvtools