• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                                      &region_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                    &region_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