1 // Copyright (c) 2020 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/fuzz/transformation_duplicate_region_with_selection.h"
16
17 #include "source/fuzz/fuzzer_util.h"
18
19 namespace spvtools {
20 namespace fuzz {
21
22 TransformationDuplicateRegionWithSelection::
TransformationDuplicateRegionWithSelection(protobufs::TransformationDuplicateRegionWithSelection message)23 TransformationDuplicateRegionWithSelection(
24 protobufs::TransformationDuplicateRegionWithSelection message)
25 : message_(std::move(message)) {}
26
27 TransformationDuplicateRegionWithSelection::
TransformationDuplicateRegionWithSelection(uint32_t new_entry_fresh_id,uint32_t condition_id,uint32_t merge_label_fresh_id,uint32_t entry_block_id,uint32_t exit_block_id,const std::map<uint32_t,uint32_t> & original_label_to_duplicate_label,const std::map<uint32_t,uint32_t> & original_id_to_duplicate_id,const std::map<uint32_t,uint32_t> & original_id_to_phi_id)28 TransformationDuplicateRegionWithSelection(
29 uint32_t new_entry_fresh_id, uint32_t condition_id,
30 uint32_t merge_label_fresh_id, uint32_t entry_block_id,
31 uint32_t exit_block_id,
32 const std::map<uint32_t, uint32_t>& original_label_to_duplicate_label,
33 const std::map<uint32_t, uint32_t>& original_id_to_duplicate_id,
34 const std::map<uint32_t, uint32_t>& original_id_to_phi_id) {
35 message_.set_new_entry_fresh_id(new_entry_fresh_id);
36 message_.set_condition_id(condition_id);
37 message_.set_merge_label_fresh_id(merge_label_fresh_id);
38 message_.set_entry_block_id(entry_block_id);
39 message_.set_exit_block_id(exit_block_id);
40 *message_.mutable_original_label_to_duplicate_label() =
41 fuzzerutil::MapToRepeatedUInt32Pair(original_label_to_duplicate_label);
42 *message_.mutable_original_id_to_duplicate_id() =
43 fuzzerutil::MapToRepeatedUInt32Pair(original_id_to_duplicate_id);
44 *message_.mutable_original_id_to_phi_id() =
45 fuzzerutil::MapToRepeatedUInt32Pair(original_id_to_phi_id);
46 }
47
IsApplicable(opt::IRContext * ir_context,const TransformationContext & transformation_context) const48 bool TransformationDuplicateRegionWithSelection::IsApplicable(
49 opt::IRContext* ir_context,
50 const TransformationContext& transformation_context) const {
51 // Instruction with the id |condition_id| must exist and must be of a bool
52 // type.
53 auto bool_instr =
54 ir_context->get_def_use_mgr()->GetDef(message_.condition_id());
55 if (bool_instr == nullptr || !bool_instr->type_id()) {
56 return false;
57 }
58 if (!ir_context->get_type_mgr()->GetType(bool_instr->type_id())->AsBool()) {
59 return false;
60 }
61
62 // The |new_entry_fresh_id| must be fresh and distinct.
63 std::set<uint32_t> ids_used_by_this_transformation;
64 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
65 message_.new_entry_fresh_id(), ir_context,
66 &ids_used_by_this_transformation)) {
67 return false;
68 }
69
70 // The |merge_label_fresh_id| must be fresh and distinct.
71 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
72 message_.merge_label_fresh_id(), ir_context,
73 &ids_used_by_this_transformation)) {
74 return false;
75 }
76
77 // The entry and exit block ids must refer to blocks.
78 for (auto block_id : {message_.entry_block_id(), message_.exit_block_id()}) {
79 auto block_label = ir_context->get_def_use_mgr()->GetDef(block_id);
80 if (!block_label || block_label->opcode() != SpvOpLabel) {
81 return false;
82 }
83 }
84 auto entry_block = ir_context->cfg()->block(message_.entry_block_id());
85 auto exit_block = ir_context->cfg()->block(message_.exit_block_id());
86
87 // The |entry_block| and the |exit_block| must be in the same function.
88 if (entry_block->GetParent() != exit_block->GetParent()) {
89 return false;
90 }
91
92 // The |entry_block| must dominate the |exit_block|.
93 auto dominator_analysis =
94 ir_context->GetDominatorAnalysis(entry_block->GetParent());
95 if (!dominator_analysis->Dominates(entry_block, exit_block)) {
96 return false;
97 }
98
99 // The |exit_block| must post-dominate the |entry_block|.
100 auto postdominator_analysis =
101 ir_context->GetPostDominatorAnalysis(entry_block->GetParent());
102 if (!postdominator_analysis->Dominates(exit_block, entry_block)) {
103 return false;
104 }
105
106 auto enclosing_function = entry_block->GetParent();
107
108 // |entry_block| cannot be the first block of the |enclosing_function|.
109 if (&*enclosing_function->begin() == entry_block) {
110 return false;
111 }
112
113 // To make the process of resolving OpPhi instructions easier, we require that
114 // the entry block has only one predecessor.
115 auto entry_block_preds = ir_context->cfg()->preds(entry_block->id());
116 std::sort(entry_block_preds.begin(), entry_block_preds.end());
117 entry_block_preds.erase(
118 std::unique(entry_block_preds.begin(), entry_block_preds.end()),
119 entry_block_preds.end());
120 if (entry_block_preds.size() > 1) {
121 return false;
122 }
123
124 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3785):
125 // The following code has been copied from TransformationOutlineFunction.
126 // Consider refactoring to avoid duplication.
127 auto region_set = GetRegionBlocks(ir_context, entry_block, exit_block);
128
129 // Check whether |region_set| really is a single-entry single-exit region, and
130 // also check whether structured control flow constructs and their merge
131 // and continue constructs are either wholly in or wholly out of the region -
132 // e.g. avoid the situation where the region contains the head of a loop but
133 // not the loop's continue construct.
134 //
135 // This is achieved by going through every block in the |enclosing_function|
136 for (auto& block : *enclosing_function) {
137 if (&block == exit_block) {
138 // It is not OK for the exit block to head a loop construct or a
139 // conditional construct.
140 if (block.GetMergeInst()) {
141 return false;
142 }
143 continue;
144 }
145 if (region_set.count(&block) != 0) {
146 // The block is in the region and is not the region's exit block. Let's
147 // see whether all of the block's successors are in the region. If they
148 // are not, the region is not single-entry single-exit.
149 bool all_successors_in_region = true;
150 block.WhileEachSuccessorLabel([&all_successors_in_region, ir_context,
151 ®ion_set](uint32_t successor) -> bool {
152 if (region_set.count(ir_context->cfg()->block(successor)) == 0) {
153 all_successors_in_region = false;
154 return false;
155 }
156 return true;
157 });
158 if (!all_successors_in_region) {
159 return false;
160 }
161 }
162
163 if (auto merge = block.GetMergeInst()) {
164 // The block is a loop or selection header. The header and its
165 // associated merge block must be both in the region or both be
166 // outside the region.
167 auto merge_block =
168 ir_context->cfg()->block(merge->GetSingleWordOperand(0));
169 if (region_set.count(&block) != region_set.count(merge_block)) {
170 return false;
171 }
172 }
173
174 if (auto loop_merge = block.GetLoopMergeInst()) {
175 // The continue target of a loop must be within the region if and only if
176 // the header of the loop is.
177 auto continue_target =
178 ir_context->cfg()->block(loop_merge->GetSingleWordOperand(1));
179 // The continue target is a single-entry, single-exit region. Therefore,
180 // if the continue target is the exit block, the region might not contain
181 // the loop header. However, we would like to exclude this situation,
182 // since it would be impossible for the modified exit block to branch to
183 // the new selection merge block. In this scenario the exit block is
184 // required to branch to the loop header.
185 if (region_set.count(&block) != region_set.count(continue_target)) {
186 return false;
187 }
188 }
189 }
190
191 // Get the maps from the protobuf.
192 std::map<uint32_t, uint32_t> original_label_to_duplicate_label =
193 fuzzerutil::RepeatedUInt32PairToMap(
194 message_.original_label_to_duplicate_label());
195
196 std::map<uint32_t, uint32_t> original_id_to_duplicate_id =
197 fuzzerutil::RepeatedUInt32PairToMap(
198 message_.original_id_to_duplicate_id());
199
200 std::map<uint32_t, uint32_t> original_id_to_phi_id =
201 fuzzerutil::RepeatedUInt32PairToMap(message_.original_id_to_phi_id());
202
203 for (auto block : region_set) {
204 // The label of every block in the region must be present in the map
205 // |original_label_to_duplicate_label|, unless overflow ids are present.
206 if (original_label_to_duplicate_label.count(block->id()) == 0) {
207 if (!transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
208 return false;
209 }
210 } else {
211 auto duplicate_label = original_label_to_duplicate_label.at(block->id());
212 // Each id assigned to labels in the region must be distinct and fresh.
213 if (!duplicate_label ||
214 !CheckIdIsFreshAndNotUsedByThisTransformation(
215 duplicate_label, ir_context, &ids_used_by_this_transformation)) {
216 return false;
217 }
218 }
219 for (auto& instr : *block) {
220 if (!instr.HasResultId()) {
221 continue;
222 }
223 // Every instruction with a result id in the region must be present in the
224 // map |original_id_to_duplicate_id|, unless overflow ids are present.
225 if (original_id_to_duplicate_id.count(instr.result_id()) == 0) {
226 if (!transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
227 return false;
228 }
229 } else {
230 auto duplicate_id = original_id_to_duplicate_id.at(instr.result_id());
231 // Id assigned to this result id in the region must be distinct and
232 // fresh.
233 if (!duplicate_id ||
234 !CheckIdIsFreshAndNotUsedByThisTransformation(
235 duplicate_id, ir_context, &ids_used_by_this_transformation)) {
236 return false;
237 }
238 }
239 // If the instruction is available at the end of the region then we would
240 // like to be able to add an OpPhi instruction at the merge point of the
241 // duplicated region to capture the values computed by both duplicates of
242 // the instruction, so that this is also available after the region. We
243 // do this not just for instructions that are already used after the
244 // region, but for all instructions so that the phi is available to future
245 // transformations.
246 if (AvailableAfterRegion(instr, exit_block, ir_context)) {
247 if (!ValidOpPhiArgument(instr, ir_context)) {
248 // The instruction cannot be used as an OpPhi argument. This is a
249 // blocker if there are uses of the instruction after the region.
250 // Otherwise we can simply avoid generating an OpPhi for this
251 // instruction and its duplicate.
252 if (!ir_context->get_def_use_mgr()->WhileEachUser(
253 &instr,
254 [ir_context,
255 ®ion_set](opt::Instruction* use_instr) -> bool {
256 opt::BasicBlock* use_block =
257 ir_context->get_instr_block(use_instr);
258 return use_block == nullptr ||
259 region_set.count(use_block) > 0;
260 })) {
261 return false;
262 }
263 } else {
264 // Every instruction with a result id available at the end of the
265 // region must be present in the map |original_id_to_phi_id|, unless
266 // overflow ids are present.
267 if (original_id_to_phi_id.count(instr.result_id()) == 0) {
268 if (!transformation_context.GetOverflowIdSource()
269 ->HasOverflowIds()) {
270 return false;
271 }
272 } else {
273 auto phi_id = original_id_to_phi_id.at(instr.result_id());
274 // Id assigned to this result id in the region must be distinct and
275 // fresh.
276 if (!phi_id ||
277 !CheckIdIsFreshAndNotUsedByThisTransformation(
278 phi_id, ir_context, &ids_used_by_this_transformation)) {
279 return false;
280 }
281 }
282 }
283 }
284 }
285 }
286 return true;
287 }
288
Apply(opt::IRContext * ir_context,TransformationContext * transformation_context) const289 void TransformationDuplicateRegionWithSelection::Apply(
290 opt::IRContext* ir_context,
291 TransformationContext* transformation_context) const {
292 fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_entry_fresh_id());
293 fuzzerutil::UpdateModuleIdBound(ir_context, message_.merge_label_fresh_id());
294
295 // Create the new entry block containing the main conditional instruction. Set
296 // its parent to the parent of the original entry block, since it is located
297 // in the same function.
298 std::unique_ptr<opt::BasicBlock> new_entry_block =
299 MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
300 ir_context, SpvOpLabel, 0, message_.new_entry_fresh_id(),
301 opt::Instruction::OperandList()));
302 auto entry_block = ir_context->cfg()->block(message_.entry_block_id());
303 auto enclosing_function = entry_block->GetParent();
304 auto exit_block = ir_context->cfg()->block(message_.exit_block_id());
305
306 // Get the blocks contained in the region.
307 std::set<opt::BasicBlock*> region_blocks =
308 GetRegionBlocks(ir_context, entry_block, exit_block);
309
310 // Construct the merge block.
311 std::unique_ptr<opt::BasicBlock> merge_block =
312 MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(opt::Instruction(
313 ir_context, SpvOpLabel, 0, message_.merge_label_fresh_id(),
314 opt::Instruction::OperandList())));
315
316 // Get the maps from the protobuf.
317 std::map<uint32_t, uint32_t> original_label_to_duplicate_label =
318 fuzzerutil::RepeatedUInt32PairToMap(
319 message_.original_label_to_duplicate_label());
320
321 std::map<uint32_t, uint32_t> original_id_to_duplicate_id =
322 fuzzerutil::RepeatedUInt32PairToMap(
323 message_.original_id_to_duplicate_id());
324
325 std::map<uint32_t, uint32_t> original_id_to_phi_id =
326 fuzzerutil::RepeatedUInt32PairToMap(message_.original_id_to_phi_id());
327
328 // Use oveflow ids to fill in any required ids that are missing from these
329 // maps.
330 for (auto block : region_blocks) {
331 if (original_label_to_duplicate_label.count(block->id()) == 0) {
332 original_label_to_duplicate_label.insert(
333 {block->id(),
334 transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
335 }
336 for (auto& instr : *block) {
337 if (!instr.HasResultId()) {
338 continue;
339 }
340 if (original_id_to_duplicate_id.count(instr.result_id()) == 0) {
341 original_id_to_duplicate_id.insert(
342 {instr.result_id(), transformation_context->GetOverflowIdSource()
343 ->GetNextOverflowId()});
344 }
345 if (AvailableAfterRegion(instr, exit_block, ir_context) &&
346 ValidOpPhiArgument(instr, ir_context)) {
347 if (original_id_to_phi_id.count(instr.result_id()) == 0) {
348 original_id_to_phi_id.insert(
349 {instr.result_id(), transformation_context->GetOverflowIdSource()
350 ->GetNextOverflowId()});
351 }
352 }
353 }
354 }
355
356 // Before adding duplicate blocks, we need to update the OpPhi instructions in
357 // the successors of the |exit_block|. We know that the execution of the
358 // transformed region will end in |merge_block|. Hence, we need to change all
359 // occurrences of the label id of the |exit_block| to the label id of the
360 // |merge_block|.
361 exit_block->ForEachSuccessorLabel([this, ir_context](uint32_t label_id) {
362 auto block = ir_context->cfg()->block(label_id);
363 for (auto& instr : *block) {
364 if (instr.opcode() == SpvOpPhi) {
365 instr.ForEachId([this](uint32_t* id) {
366 if (*id == message_.exit_block_id()) {
367 *id = message_.merge_label_fresh_id();
368 }
369 });
370 }
371 }
372 });
373
374 // Get vector of predecessors id of |entry_block|. Remove any duplicate
375 // values.
376 auto entry_block_preds = ir_context->cfg()->preds(entry_block->id());
377 std::sort(entry_block_preds.begin(), entry_block_preds.end());
378 entry_block_preds.erase(
379 unique(entry_block_preds.begin(), entry_block_preds.end()),
380 entry_block_preds.end());
381 // We know that |entry_block| has only one predecessor, since the region is
382 // single-entry, single-exit and its constructs and their merge blocks must be
383 // either wholly within or wholly outside of the region.
384 assert(entry_block_preds.size() == 1 &&
385 "The entry of the region to be duplicated can have only one "
386 "predecessor.");
387 uint32_t entry_block_pred_id =
388 ir_context->get_instr_block(entry_block_preds[0])->id();
389 // Update all the OpPhi instructions in the |entry_block|. Change every
390 // occurrence of |entry_block_pred_id| to the id of |new_entry|, because we
391 // will insert |new_entry| before |entry_block|.
392 for (auto& instr : *entry_block) {
393 if (instr.opcode() == SpvOpPhi) {
394 instr.ForEachId([this, entry_block_pred_id](uint32_t* id) {
395 if (*id == entry_block_pred_id) {
396 *id = message_.new_entry_fresh_id();
397 }
398 });
399 }
400 }
401
402 // Duplication of blocks will invalidate iterators. Store all the blocks from
403 // the enclosing function.
404 std::vector<opt::BasicBlock*> blocks;
405 for (auto& block : *enclosing_function) {
406 blocks.push_back(&block);
407 }
408
409 opt::BasicBlock* previous_block = nullptr;
410 opt::BasicBlock* duplicated_exit_block = nullptr;
411 // Iterate over all blocks of the function to duplicate blocks of the original
412 // region and their instructions.
413 for (auto& block : blocks) {
414 // The block must be contained in the region.
415 if (region_blocks.count(block) == 0) {
416 continue;
417 }
418
419 fuzzerutil::UpdateModuleIdBound(
420 ir_context, original_label_to_duplicate_label.at(block->id()));
421
422 std::unique_ptr<opt::BasicBlock> duplicated_block =
423 MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
424 ir_context, SpvOpLabel, 0,
425 original_label_to_duplicate_label.at(block->id()),
426 opt::Instruction::OperandList()));
427
428 for (auto& instr : *block) {
429 // Case where an instruction is the terminator of the exit block is
430 // handled separately.
431 if (block == exit_block && instr.IsBlockTerminator()) {
432 switch (instr.opcode()) {
433 case SpvOpBranch:
434 case SpvOpBranchConditional:
435 case SpvOpReturn:
436 case SpvOpReturnValue:
437 case SpvOpUnreachable:
438 case SpvOpKill:
439 continue;
440 default:
441 assert(false &&
442 "Unexpected terminator for |exit_block| of the region.");
443 }
444 }
445 // Duplicate the instruction.
446 auto cloned_instr = instr.Clone(ir_context);
447 duplicated_block->AddInstruction(
448 std::unique_ptr<opt::Instruction>(cloned_instr));
449
450 if (instr.HasResultId()) {
451 fuzzerutil::UpdateModuleIdBound(
452 ir_context, original_id_to_duplicate_id.at(instr.result_id()));
453 }
454
455 // If an id from the original region was used in this instruction,
456 // replace it with the value from |original_id_to_duplicate_id|.
457 // If a label from the original region was used in this instruction,
458 // replace it with the value from |original_label_to_duplicate_label|.
459 cloned_instr->ForEachId(
460 [original_id_to_duplicate_id,
461 original_label_to_duplicate_label](uint32_t* op) {
462 if (original_id_to_duplicate_id.count(*op) != 0) {
463 *op = original_id_to_duplicate_id.at(*op);
464 } else if (original_label_to_duplicate_label.count(*op) != 0) {
465 *op = original_label_to_duplicate_label.at(*op);
466 }
467 });
468 }
469
470 // If the block is the first duplicated block, insert it after the exit
471 // block of the original region. Otherwise, insert it after the preceding
472 // one.
473 auto duplicated_block_ptr = duplicated_block.get();
474 if (previous_block) {
475 enclosing_function->InsertBasicBlockAfter(std::move(duplicated_block),
476 previous_block);
477 } else {
478 enclosing_function->InsertBasicBlockAfter(std::move(duplicated_block),
479 exit_block);
480 }
481 previous_block = duplicated_block_ptr;
482 if (block == exit_block) {
483 // After execution of the loop, this variable stores a pointer to the last
484 // duplicated block.
485 duplicated_exit_block = duplicated_block_ptr;
486 }
487 }
488
489 for (auto& block : region_blocks) {
490 for (auto& instr : *block) {
491 if (instr.result_id() == 0) {
492 continue;
493 }
494 if (AvailableAfterRegion(instr, exit_block, ir_context) &&
495 ValidOpPhiArgument(instr, ir_context)) {
496 // Add an OpPhi instruction for every result id that is available at
497 // the end of the region, as long as the result id is valid for use
498 // with OpPhi.
499 merge_block->AddInstruction(MakeUnique<opt::Instruction>(
500 ir_context, SpvOpPhi, instr.type_id(),
501 original_id_to_phi_id.at(instr.result_id()),
502 opt::Instruction::OperandList({
503 {SPV_OPERAND_TYPE_ID, {instr.result_id()}},
504 {SPV_OPERAND_TYPE_ID, {exit_block->id()}},
505 {SPV_OPERAND_TYPE_ID,
506 {original_id_to_duplicate_id.at(instr.result_id())}},
507 {SPV_OPERAND_TYPE_ID, {duplicated_exit_block->id()}},
508 })));
509
510 fuzzerutil::UpdateModuleIdBound(
511 ir_context, original_id_to_phi_id.at(instr.result_id()));
512
513 // If the instruction has been remapped by an OpPhi, look
514 // for all its uses outside of the region and outside of the
515 // merge block (to not overwrite just added instructions in
516 // the merge block) and replace the original instruction id
517 // with the id of the corresponding OpPhi instruction.
518 ir_context->get_def_use_mgr()->ForEachUse(
519 &instr,
520 [ir_context, &instr, region_blocks, original_id_to_phi_id,
521 &merge_block](opt::Instruction* user, uint32_t operand_index) {
522 auto user_block = ir_context->get_instr_block(user);
523 if ((region_blocks.find(user_block) != region_blocks.end()) ||
524 user_block == merge_block.get()) {
525 return;
526 }
527 user->SetOperand(operand_index,
528 {original_id_to_phi_id.at(instr.result_id())});
529 });
530 }
531 }
532 }
533
534 // Construct a conditional instruction in the |new_entry_block|.
535 // If the condition is true, the execution proceeds in the
536 // |entry_block| of the original region. If the condition is
537 // false, the execution proceeds in the first block of the
538 // duplicated region.
539 new_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
540 ir_context, SpvOpSelectionMerge, 0, 0,
541 opt::Instruction::OperandList(
542 {{SPV_OPERAND_TYPE_ID, {message_.merge_label_fresh_id()}},
543 {SPV_OPERAND_TYPE_SELECTION_CONTROL,
544 {SpvSelectionControlMaskNone}}})));
545
546 new_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
547 ir_context, SpvOpBranchConditional, 0, 0,
548 opt::Instruction::OperandList(
549 {{SPV_OPERAND_TYPE_ID, {message_.condition_id()}},
550 {SPV_OPERAND_TYPE_ID, {message_.entry_block_id()}},
551 {SPV_OPERAND_TYPE_ID,
552 {original_label_to_duplicate_label.at(
553 message_.entry_block_id())}}})));
554
555 // Move the terminator of |exit_block| to the end of
556 // |merge_block|.
557 auto exit_block_terminator = exit_block->terminator();
558 auto cloned_instr = exit_block_terminator->Clone(ir_context);
559 merge_block->AddInstruction(std::unique_ptr<opt::Instruction>(cloned_instr));
560 ir_context->KillInst(exit_block_terminator);
561
562 // Add OpBranch instruction to the merge block at the end of
563 // |exit_block| and at the end of |duplicated_exit_block|, so that
564 // the execution proceeds in the |merge_block|.
565 opt::Instruction merge_branch_instr = opt::Instruction(
566 ir_context, SpvOpBranch, 0, 0,
567 opt::Instruction::OperandList(
568 {{SPV_OPERAND_TYPE_ID, {message_.merge_label_fresh_id()}}}));
569 exit_block->AddInstruction(MakeUnique<opt::Instruction>(merge_branch_instr));
570 duplicated_exit_block->AddInstruction(
571 std::unique_ptr<opt::Instruction>(merge_branch_instr.Clone(ir_context)));
572
573 // Execution needs to start in the |new_entry_block|. Change all
574 // the uses of |entry_block_label_instr| outside of the original
575 // region to |message_.new_entry_fresh_id|.
576 auto entry_block_label_instr =
577 ir_context->get_def_use_mgr()->GetDef(message_.entry_block_id());
578 ir_context->get_def_use_mgr()->ForEachUse(
579 entry_block_label_instr,
580 [this, ir_context, region_blocks](opt::Instruction* user,
581 uint32_t operand_index) {
582 auto user_block = ir_context->get_instr_block(user);
583 if ((region_blocks.count(user_block) != 0)) {
584 return;
585 }
586 switch (user->opcode()) {
587 case SpvOpSwitch:
588 case SpvOpBranch:
589 case SpvOpBranchConditional:
590 case SpvOpLoopMerge:
591 case SpvOpSelectionMerge: {
592 user->SetOperand(operand_index, {message_.new_entry_fresh_id()});
593 } break;
594 case SpvOpName:
595 break;
596 default:
597 assert(false &&
598 "The label id cannot be used by instructions "
599 "other than "
600 "OpSwitch, OpBranch, OpBranchConditional, "
601 "OpLoopMerge, "
602 "OpSelectionMerge");
603 }
604 });
605
606 opt::Instruction* merge_block_terminator = merge_block->terminator();
607 switch (merge_block_terminator->opcode()) {
608 case SpvOpReturnValue:
609 case SpvOpBranchConditional: {
610 uint32_t operand = merge_block_terminator->GetSingleWordInOperand(0);
611 if (original_id_to_phi_id.count(operand)) {
612 merge_block_terminator->SetInOperand(
613 0, {original_id_to_phi_id.at(operand)});
614 }
615 break;
616 }
617 default:
618 break;
619 }
620
621 // Insert the merge block after the |duplicated_exit_block| (the
622 // last duplicated block).
623 enclosing_function->InsertBasicBlockAfter(std::move(merge_block),
624 duplicated_exit_block);
625
626 // Insert the |new_entry_block| before the entry block of the
627 // original region.
628 enclosing_function->InsertBasicBlockBefore(std::move(new_entry_block),
629 entry_block);
630
631 // Since we have changed the module, most of the analysis are now
632 // invalid. We can invalidate analyses now after all of the blocks
633 // have been registered.
634 ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
635 }
636
637 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3785):
638 // The following method has been copied from
639 // TransformationOutlineFunction. Consider refactoring to avoid
640 // duplication.
641 std::set<opt::BasicBlock*>
GetRegionBlocks(opt::IRContext * ir_context,opt::BasicBlock * entry_block,opt::BasicBlock * exit_block)642 TransformationDuplicateRegionWithSelection::GetRegionBlocks(
643 opt::IRContext* ir_context, opt::BasicBlock* entry_block,
644 opt::BasicBlock* exit_block) {
645 auto enclosing_function = entry_block->GetParent();
646 auto dominator_analysis =
647 ir_context->GetDominatorAnalysis(enclosing_function);
648 auto postdominator_analysis =
649 ir_context->GetPostDominatorAnalysis(enclosing_function);
650
651 // A block belongs to a region between the entry block and the exit
652 // block if and only if it is dominated by the entry block and
653 // post-dominated by the exit block.
654 std::set<opt::BasicBlock*> result;
655 for (auto& block : *enclosing_function) {
656 if (dominator_analysis->Dominates(entry_block, &block) &&
657 postdominator_analysis->Dominates(exit_block, &block)) {
658 result.insert(&block);
659 }
660 }
661 return result;
662 }
663
664 protobufs::Transformation
ToMessage() const665 TransformationDuplicateRegionWithSelection::ToMessage() const {
666 protobufs::Transformation result;
667 *result.mutable_duplicate_region_with_selection() = message_;
668 return result;
669 }
670
671 std::unordered_set<uint32_t>
GetFreshIds() const672 TransformationDuplicateRegionWithSelection::GetFreshIds() const {
673 std::unordered_set<uint32_t> result = {message_.new_entry_fresh_id(),
674 message_.merge_label_fresh_id()};
675 for (auto& pair : message_.original_label_to_duplicate_label()) {
676 result.insert(pair.second());
677 }
678 for (auto& pair : message_.original_id_to_duplicate_id()) {
679 result.insert(pair.second());
680 }
681 for (auto& pair : message_.original_id_to_phi_id()) {
682 result.insert(pair.second());
683 }
684 return result;
685 }
686
AvailableAfterRegion(const opt::Instruction & instr,opt::BasicBlock * exit_block,opt::IRContext * ir_context)687 bool TransformationDuplicateRegionWithSelection::AvailableAfterRegion(
688 const opt::Instruction& instr, opt::BasicBlock* exit_block,
689 opt::IRContext* ir_context) {
690 opt::Instruction* final_instruction_in_region = &*exit_block->tail();
691 return &instr == final_instruction_in_region ||
692 fuzzerutil::IdIsAvailableBeforeInstruction(
693 ir_context, final_instruction_in_region, instr.result_id());
694 }
695
ValidOpPhiArgument(const opt::Instruction & instr,opt::IRContext * ir_context)696 bool TransformationDuplicateRegionWithSelection::ValidOpPhiArgument(
697 const opt::Instruction& instr, opt::IRContext* ir_context) {
698 opt::Instruction* instr_type =
699 ir_context->get_def_use_mgr()->GetDef(instr.type_id());
700
701 // It is invalid to apply OpPhi to void-typed values.
702 if (instr_type->opcode() == SpvOpTypeVoid) {
703 return false;
704 }
705
706 // Using pointers with OpPhi requires capability VariablePointers.
707 if (instr_type->opcode() == SpvOpTypePointer &&
708 !ir_context->get_feature_mgr()->HasCapability(
709 SpvCapabilityVariablePointers)) {
710 return false;
711 }
712
713 // OpTypeSampledImage cannot be the result type of an OpPhi instruction.
714 if (instr_type->opcode() == SpvOpTypeSampledImage) {
715 return false;
716 }
717 return true;
718 }
719
720 } // namespace fuzz
721 } // namespace spvtools
722