• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2019 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_outline_function.h"
16 
17 #include <set>
18 
19 #include "source/fuzz/fuzzer_util.h"
20 
21 namespace spvtools {
22 namespace fuzz {
23 
TransformationOutlineFunction(protobufs::TransformationOutlineFunction message)24 TransformationOutlineFunction::TransformationOutlineFunction(
25     protobufs::TransformationOutlineFunction message)
26     : message_(std::move(message)) {}
27 
TransformationOutlineFunction(uint32_t entry_block,uint32_t exit_block,uint32_t new_function_struct_return_type_id,uint32_t new_function_type_id,uint32_t new_function_id,uint32_t new_function_region_entry_block,uint32_t new_caller_result_id,uint32_t new_callee_result_id,const std::map<uint32_t,uint32_t> & input_id_to_fresh_id,const std::map<uint32_t,uint32_t> & output_id_to_fresh_id)28 TransformationOutlineFunction::TransformationOutlineFunction(
29     uint32_t entry_block, uint32_t exit_block,
30     uint32_t new_function_struct_return_type_id, uint32_t new_function_type_id,
31     uint32_t new_function_id, uint32_t new_function_region_entry_block,
32     uint32_t new_caller_result_id, uint32_t new_callee_result_id,
33     const std::map<uint32_t, uint32_t>& input_id_to_fresh_id,
34     const std::map<uint32_t, uint32_t>& output_id_to_fresh_id) {
35   message_.set_entry_block(entry_block);
36   message_.set_exit_block(exit_block);
37   message_.set_new_function_struct_return_type_id(
38       new_function_struct_return_type_id);
39   message_.set_new_function_type_id(new_function_type_id);
40   message_.set_new_function_id(new_function_id);
41   message_.set_new_function_region_entry_block(new_function_region_entry_block);
42   message_.set_new_caller_result_id(new_caller_result_id);
43   message_.set_new_callee_result_id(new_callee_result_id);
44   *message_.mutable_input_id_to_fresh_id() =
45       fuzzerutil::MapToRepeatedUInt32Pair(input_id_to_fresh_id);
46   *message_.mutable_output_id_to_fresh_id() =
47       fuzzerutil::MapToRepeatedUInt32Pair(output_id_to_fresh_id);
48 }
49 
IsApplicable(opt::IRContext * ir_context,const TransformationContext & transformation_context) const50 bool TransformationOutlineFunction::IsApplicable(
51     opt::IRContext* ir_context,
52     const TransformationContext& transformation_context) const {
53   std::set<uint32_t> ids_used_by_this_transformation;
54 
55   // The various new ids used by the transformation must be fresh and distinct.
56 
57   if (!CheckIdIsFreshAndNotUsedByThisTransformation(
58           message_.new_function_struct_return_type_id(), ir_context,
59           &ids_used_by_this_transformation)) {
60     return false;
61   }
62 
63   if (!CheckIdIsFreshAndNotUsedByThisTransformation(
64           message_.new_function_type_id(), ir_context,
65           &ids_used_by_this_transformation)) {
66     return false;
67   }
68 
69   if (!CheckIdIsFreshAndNotUsedByThisTransformation(
70           message_.new_function_id(), ir_context,
71           &ids_used_by_this_transformation)) {
72     return false;
73   }
74 
75   if (!CheckIdIsFreshAndNotUsedByThisTransformation(
76           message_.new_function_region_entry_block(), ir_context,
77           &ids_used_by_this_transformation)) {
78     return false;
79   }
80 
81   if (!CheckIdIsFreshAndNotUsedByThisTransformation(
82           message_.new_caller_result_id(), ir_context,
83           &ids_used_by_this_transformation)) {
84     return false;
85   }
86 
87   if (!CheckIdIsFreshAndNotUsedByThisTransformation(
88           message_.new_callee_result_id(), ir_context,
89           &ids_used_by_this_transformation)) {
90     return false;
91   }
92 
93   for (auto& pair : message_.input_id_to_fresh_id()) {
94     if (!CheckIdIsFreshAndNotUsedByThisTransformation(
95             pair.second(), ir_context, &ids_used_by_this_transformation)) {
96       return false;
97     }
98   }
99 
100   for (auto& pair : message_.output_id_to_fresh_id()) {
101     if (!CheckIdIsFreshAndNotUsedByThisTransformation(
102             pair.second(), ir_context, &ids_used_by_this_transformation)) {
103       return false;
104     }
105   }
106 
107   // The entry and exit block ids must indeed refer to blocks.
108   for (auto block_id : {message_.entry_block(), message_.exit_block()}) {
109     auto block_label = ir_context->get_def_use_mgr()->GetDef(block_id);
110     if (!block_label || block_label->opcode() != spv::Op::OpLabel) {
111       return false;
112     }
113   }
114 
115   auto entry_block = ir_context->cfg()->block(message_.entry_block());
116   auto exit_block = ir_context->cfg()->block(message_.exit_block());
117 
118   // The entry block cannot start with OpVariable - this would mean that
119   // outlining would remove a variable from the function containing the region
120   // being outlined.
121   if (entry_block->begin()->opcode() == spv::Op::OpVariable) {
122     return false;
123   }
124 
125   // For simplicity, we do not allow the entry block to be a loop header.
126   if (entry_block->GetLoopMergeInst()) {
127     return false;
128   }
129 
130   // For simplicity, we do not allow the exit block to be a merge block or
131   // continue target.
132   if (fuzzerutil::IsMergeOrContinue(ir_context, exit_block->id())) {
133     return false;
134   }
135 
136   // The entry block cannot start with OpPhi.  This is to keep the
137   // transformation logic simple.  (Another transformation to split the OpPhis
138   // from a block could be applied to avoid this scenario.)
139   if (entry_block->begin()->opcode() == spv::Op::OpPhi) {
140     return false;
141   }
142 
143   // The block must be in the same function.
144   if (entry_block->GetParent() != exit_block->GetParent()) {
145     return false;
146   }
147 
148   // The entry block must dominate the exit block.
149   auto dominator_analysis =
150       ir_context->GetDominatorAnalysis(entry_block->GetParent());
151   if (!dominator_analysis->Dominates(entry_block, exit_block)) {
152     return false;
153   }
154 
155   // The exit block must post-dominate the entry block.
156   auto postdominator_analysis =
157       ir_context->GetPostDominatorAnalysis(entry_block->GetParent());
158   if (!postdominator_analysis->Dominates(exit_block, entry_block)) {
159     return false;
160   }
161 
162   // Find all the blocks dominated by |message_.entry_block| and post-dominated
163   // by |message_.exit_block|.
164   auto region_set = GetRegionBlocks(
165       ir_context,
166       entry_block = ir_context->cfg()->block(message_.entry_block()),
167       exit_block = ir_context->cfg()->block(message_.exit_block()));
168 
169   // Check whether |region_set| really is a single-entry single-exit region, and
170   // also check whether structured control flow constructs and their merge
171   // and continue constructs are either wholly in or wholly out of the region -
172   // e.g. avoid the situation where the region contains the head of a loop but
173   // not the loop's continue construct.
174   //
175   // This is achieved by going through every block in the function that contains
176   // the region.
177   for (auto& block : *entry_block->GetParent()) {
178     if (region_set.count(&block) != 0) {
179       // The block is in the region. Check that it does not have any unreachable
180       // predecessors. If it does, then we do not regard the region as single-
181       // entry-single-exit and hence do not outline it.
182       for (auto pred : ir_context->cfg()->preds(block.id())) {
183         if (!ir_context->IsReachable(*ir_context->cfg()->block(pred))) {
184           // The predecessor is unreachable.
185           return false;
186         }
187       }
188     }
189 
190     if (&block == exit_block) {
191       // It is OK (and typically expected) for the exit block of the region to
192       // have successors outside the region.
193       //
194       // It is also OK for the exit block to head a selection construct: the
195       // block containing the call to the outlined function will end up heading
196       // this construct if outlining takes place.  However, it is not OK for
197       // the exit block to head a loop construct.
198       if (block.GetLoopMergeInst()) {
199         return false;
200       }
201       continue;
202     }
203 
204     if (region_set.count(&block) != 0) {
205       // The block is in the region and is not the region's exit block.  Let's
206       // see whether all of the block's successors are in the region.  If they
207       // are not, the region is not single-entry single-exit.
208       bool all_successors_in_region = true;
209       block.WhileEachSuccessorLabel([&all_successors_in_region, ir_context,
210                                      &region_set](uint32_t successor) -> bool {
211         if (region_set.count(ir_context->cfg()->block(successor)) == 0) {
212           all_successors_in_region = false;
213           return false;
214         }
215         return true;
216       });
217       if (!all_successors_in_region) {
218         return false;
219       }
220     }
221 
222     if (auto merge = block.GetMergeInst()) {
223       // The block is a loop or selection header -- the header and its
224       // associated merge block had better both be in the region or both be
225       // outside the region.
226       auto merge_block =
227           ir_context->cfg()->block(merge->GetSingleWordOperand(0));
228       if (region_set.count(&block) != region_set.count(merge_block)) {
229         return false;
230       }
231     }
232 
233     if (auto loop_merge = block.GetLoopMergeInst()) {
234       // Similar to the above, but for the continue target of a loop.
235       auto continue_target =
236           ir_context->cfg()->block(loop_merge->GetSingleWordOperand(1));
237       if (continue_target != exit_block &&
238           region_set.count(&block) != region_set.count(continue_target)) {
239         return false;
240       }
241     }
242   }
243 
244   // For each region input id, i.e. every id defined outside the region but
245   // used inside the region, ...
246   auto input_id_to_fresh_id_map =
247       fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id());
248   for (auto id : GetRegionInputIds(ir_context, region_set, exit_block)) {
249     // There needs to be a corresponding fresh id to be used as a function
250     // parameter, or overflow ids need to be available.
251     if (input_id_to_fresh_id_map.count(id) == 0 &&
252         !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
253       return false;
254     }
255     // Furthermore, if the input id has pointer type it must be an OpVariable
256     // or OpFunctionParameter.
257     auto input_id_inst = ir_context->get_def_use_mgr()->GetDef(id);
258     if (ir_context->get_def_use_mgr()
259             ->GetDef(input_id_inst->type_id())
260             ->opcode() == spv::Op::OpTypePointer) {
261       switch (input_id_inst->opcode()) {
262         case spv::Op::OpFunctionParameter:
263         case spv::Op::OpVariable:
264           // These are OK.
265           break;
266         default:
267           // Anything else is not OK.
268           return false;
269       }
270     }
271   }
272 
273   // For each region output id -- i.e. every id defined inside the region but
274   // used outside the region, ...
275   auto output_id_to_fresh_id_map =
276       fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id());
277   for (auto id : GetRegionOutputIds(ir_context, region_set, exit_block)) {
278     if (
279         // ... there needs to be a corresponding fresh id that can hold the
280         // value for this id computed in the outlined function (or overflow ids
281         // must be available), and ...
282         (output_id_to_fresh_id_map.count(id) == 0 &&
283          !transformation_context.GetOverflowIdSource()->HasOverflowIds())
284         // ... the output id must not have pointer type (to avoid creating a
285         // struct with pointer members to pass data out of the outlined
286         // function)
287         || ir_context->get_def_use_mgr()
288                    ->GetDef(fuzzerutil::GetTypeId(ir_context, id))
289                    ->opcode() == spv::Op::OpTypePointer) {
290       return false;
291     }
292   }
293 
294   return true;
295 }
296 
Apply(opt::IRContext * ir_context,TransformationContext * transformation_context) const297 void TransformationOutlineFunction::Apply(
298     opt::IRContext* ir_context,
299     TransformationContext* transformation_context) const {
300   // The entry block for the region before outlining.
301   auto original_region_entry_block =
302       ir_context->cfg()->block(message_.entry_block());
303 
304   // The exit block for the region before outlining.
305   auto original_region_exit_block =
306       ir_context->cfg()->block(message_.exit_block());
307 
308   // The single-entry single-exit region defined by |message_.entry_block| and
309   // |message_.exit_block|.
310   std::set<opt::BasicBlock*> region_blocks = GetRegionBlocks(
311       ir_context, original_region_entry_block, original_region_exit_block);
312 
313   // Input and output ids for the region being outlined.
314   std::vector<uint32_t> region_input_ids =
315       GetRegionInputIds(ir_context, region_blocks, original_region_exit_block);
316   std::vector<uint32_t> region_output_ids =
317       GetRegionOutputIds(ir_context, region_blocks, original_region_exit_block);
318 
319   // Maps from input and output ids to fresh ids.
320   auto input_id_to_fresh_id_map =
321       fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id());
322   auto output_id_to_fresh_id_map =
323       fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id());
324 
325   // Use overflow ids to augment these maps at any locations where fresh ids are
326   // required but not provided.
327   for (uint32_t id : region_input_ids) {
328     if (input_id_to_fresh_id_map.count(id) == 0) {
329       input_id_to_fresh_id_map.insert(
330           {id,
331            transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
332     }
333   }
334   for (uint32_t id : region_output_ids) {
335     if (output_id_to_fresh_id_map.count(id) == 0) {
336       output_id_to_fresh_id_map.insert(
337           {id,
338            transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
339     }
340   }
341 
342   UpdateModuleIdBoundForFreshIds(ir_context, input_id_to_fresh_id_map,
343                                  output_id_to_fresh_id_map);
344 
345   // Construct a map that associates each output id with its type id.
346   std::map<uint32_t, uint32_t> output_id_to_type_id;
347   for (uint32_t output_id : region_output_ids) {
348     output_id_to_type_id[output_id] =
349         ir_context->get_def_use_mgr()->GetDef(output_id)->type_id();
350   }
351 
352   // The region will be collapsed to a single block that calls a function
353   // containing the outlined region.  This block needs to end with whatever
354   // the exit block of the region ended with before outlining.  We thus clone
355   // the terminator of the region's exit block, and the merge instruction for
356   // the block if there is one, so that we can append them to the end of the
357   // collapsed block later.
358   std::unique_ptr<opt::Instruction> cloned_exit_block_terminator =
359       std::unique_ptr<opt::Instruction>(
360           original_region_exit_block->terminator()->Clone(ir_context));
361   std::unique_ptr<opt::Instruction> cloned_exit_block_merge =
362       original_region_exit_block->GetMergeInst()
363           ? std::unique_ptr<opt::Instruction>(
364                 original_region_exit_block->GetMergeInst()->Clone(ir_context))
365           : nullptr;
366 
367   // Make a function prototype for the outlined function, which involves
368   // figuring out its required type.
369   std::unique_ptr<opt::Function> outlined_function = PrepareFunctionPrototype(
370       region_input_ids, region_output_ids, input_id_to_fresh_id_map, ir_context,
371       transformation_context);
372 
373   // Adapt the region to be outlined so that its input ids are replaced with the
374   // ids of the outlined function's input parameters, and so that output ids
375   // are similarly remapped.
376   RemapInputAndOutputIdsInRegion(
377       ir_context, *original_region_exit_block, region_blocks, region_input_ids,
378       region_output_ids, input_id_to_fresh_id_map, output_id_to_fresh_id_map);
379 
380   // Fill out the body of the outlined function according to the region that is
381   // being outlined.
382   PopulateOutlinedFunction(
383       *original_region_entry_block, *original_region_exit_block, region_blocks,
384       region_output_ids, output_id_to_type_id, output_id_to_fresh_id_map,
385       ir_context, outlined_function.get());
386 
387   // Collapse the region that has been outlined into a function down to a single
388   // block that calls said function.
389   ShrinkOriginalRegion(
390       ir_context, region_blocks, region_input_ids, region_output_ids,
391       output_id_to_type_id, outlined_function->type_id(),
392       std::move(cloned_exit_block_merge),
393       std::move(cloned_exit_block_terminator), original_region_entry_block);
394 
395   // Add the outlined function to the module.
396   const auto* outlined_function_ptr = outlined_function.get();
397   ir_context->module()->AddFunction(std::move(outlined_function));
398 
399   // Major surgery has been conducted on the module, so invalidate all analyses.
400   ir_context->InvalidateAnalysesExceptFor(
401       opt::IRContext::Analysis::kAnalysisNone);
402 
403   // If the original function was livesafe, the new function should also be
404   // livesafe.
405   if (transformation_context->GetFactManager()->FunctionIsLivesafe(
406           original_region_entry_block->GetParent()->result_id())) {
407     transformation_context->GetFactManager()->AddFactFunctionIsLivesafe(
408         message_.new_function_id());
409   }
410 
411   // Record the fact that all blocks in the outlined region are dead if the
412   // first block is dead.
413   if (transformation_context->GetFactManager()->BlockIsDead(
414           original_region_entry_block->id())) {
415     transformation_context->GetFactManager()->AddFactBlockIsDead(
416         outlined_function_ptr->entry()->id());
417   }
418 }
419 
ToMessage() const420 protobufs::Transformation TransformationOutlineFunction::ToMessage() const {
421   protobufs::Transformation result;
422   *result.mutable_outline_function() = message_;
423   return result;
424 }
425 
GetRegionInputIds(opt::IRContext * ir_context,const std::set<opt::BasicBlock * > & region_set,opt::BasicBlock * region_exit_block)426 std::vector<uint32_t> TransformationOutlineFunction::GetRegionInputIds(
427     opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_set,
428     opt::BasicBlock* region_exit_block) {
429   std::vector<uint32_t> result;
430 
431   auto enclosing_function = region_exit_block->GetParent();
432 
433   // Consider each parameter of the function containing the region.
434   enclosing_function->ForEachParam(
435       [ir_context, &region_set, &result](opt::Instruction* function_parameter) {
436         // Consider every use of the parameter.
437         ir_context->get_def_use_mgr()->WhileEachUse(
438             function_parameter,
439             [ir_context, function_parameter, &region_set, &result](
440                 opt::Instruction* use, uint32_t /*unused*/) {
441               // Get the block, if any, in which the parameter is used.
442               auto use_block = ir_context->get_instr_block(use);
443               // If the use is in a block that lies within the region, the
444               // parameter is an input id for the region.
445               if (use_block && region_set.count(use_block) != 0) {
446                 result.push_back(function_parameter->result_id());
447                 return false;
448               }
449               return true;
450             });
451       });
452 
453   // Consider all definitions in the function that might turn out to be input
454   // ids.
455   for (auto& block : *enclosing_function) {
456     std::vector<opt::Instruction*> candidate_input_ids_for_block;
457     if (region_set.count(&block) == 0) {
458       // All instructions in blocks outside the region are candidate's for
459       // generating input ids.
460       for (auto& inst : block) {
461         candidate_input_ids_for_block.push_back(&inst);
462       }
463     } else {
464       // Blocks in the region cannot generate input ids.
465       continue;
466     }
467 
468     // Consider each candidate input id to check whether it is used in the
469     // region.
470     for (auto& inst : candidate_input_ids_for_block) {
471       ir_context->get_def_use_mgr()->WhileEachUse(
472           inst,
473           [ir_context, &inst, region_exit_block, &region_set, &result](
474               opt::Instruction* use, uint32_t /*unused*/) -> bool {
475             // Find the block in which this id use occurs, recording the id as
476             // an input id if the block is outside the region, with some
477             // exceptions detailed below.
478             auto use_block = ir_context->get_instr_block(use);
479 
480             if (!use_block) {
481               // There might be no containing block, e.g. if the use is in a
482               // decoration.
483               return true;
484             }
485 
486             if (region_set.count(use_block) == 0) {
487               // The use is not in the region: this does not make it an input
488               // id.
489               return true;
490             }
491 
492             if (use_block == region_exit_block && use->IsBlockTerminator()) {
493               // We do not regard uses in the exit block terminator as input
494               // ids, as this terminator does not get outlined.
495               return true;
496             }
497 
498             result.push_back(inst->result_id());
499             return false;
500           });
501     }
502   }
503   return result;
504 }
505 
GetRegionOutputIds(opt::IRContext * ir_context,const std::set<opt::BasicBlock * > & region_set,opt::BasicBlock * region_exit_block)506 std::vector<uint32_t> TransformationOutlineFunction::GetRegionOutputIds(
507     opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_set,
508     opt::BasicBlock* region_exit_block) {
509   std::vector<uint32_t> result;
510 
511   // Consider each block in the function containing the region.
512   for (auto& block : *region_exit_block->GetParent()) {
513     if (region_set.count(&block) == 0) {
514       // Skip blocks that are not in the region.
515       continue;
516     }
517     // Consider each use of each instruction defined in the block.
518     for (auto& inst : block) {
519       ir_context->get_def_use_mgr()->WhileEachUse(
520           &inst,
521           [&region_set, ir_context, &inst, region_exit_block, &result](
522               opt::Instruction* use, uint32_t /*unused*/) -> bool {
523             // Find the block in which this id use occurs, recording the id as
524             // an output id if the block is outside the region, with some
525             // exceptions detailed below.
526             auto use_block = ir_context->get_instr_block(use);
527 
528             if (!use_block) {
529               // There might be no containing block, e.g. if the use is in a
530               // decoration.
531               return true;
532             }
533 
534             if (region_set.count(use_block) != 0) {
535               // The use is in the region.
536               if (use_block != region_exit_block || !use->IsBlockTerminator()) {
537                 // Furthermore, the use is not in the terminator of the region's
538                 // exit block.
539                 return true;
540               }
541             }
542 
543             result.push_back(inst.result_id());
544             return false;
545           });
546     }
547   }
548   return result;
549 }
550 
GetRegionBlocks(opt::IRContext * ir_context,opt::BasicBlock * entry_block,opt::BasicBlock * exit_block)551 std::set<opt::BasicBlock*> TransformationOutlineFunction::GetRegionBlocks(
552     opt::IRContext* ir_context, opt::BasicBlock* entry_block,
553     opt::BasicBlock* exit_block) {
554   auto enclosing_function = entry_block->GetParent();
555   auto dominator_analysis =
556       ir_context->GetDominatorAnalysis(enclosing_function);
557   auto postdominator_analysis =
558       ir_context->GetPostDominatorAnalysis(enclosing_function);
559 
560   std::set<opt::BasicBlock*> result;
561   for (auto& block : *enclosing_function) {
562     if (dominator_analysis->Dominates(entry_block, &block) &&
563         postdominator_analysis->Dominates(exit_block, &block)) {
564       result.insert(&block);
565     }
566   }
567   return result;
568 }
569 
570 std::unique_ptr<opt::Function>
PrepareFunctionPrototype(const std::vector<uint32_t> & region_input_ids,const std::vector<uint32_t> & region_output_ids,const std::map<uint32_t,uint32_t> & input_id_to_fresh_id_map,opt::IRContext * ir_context,TransformationContext * transformation_context) const571 TransformationOutlineFunction::PrepareFunctionPrototype(
572     const std::vector<uint32_t>& region_input_ids,
573     const std::vector<uint32_t>& region_output_ids,
574     const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
575     opt::IRContext* ir_context,
576     TransformationContext* transformation_context) const {
577   uint32_t return_type_id = 0;
578   uint32_t function_type_id = 0;
579 
580   // First, try to find an existing function type that is suitable.  This is
581   // only possible if the region generates no output ids; if it generates output
582   // ids we are going to make a new struct for those, and since that struct does
583   // not exist there cannot already be a function type with this struct as its
584   // return type.
585   if (region_output_ids.empty()) {
586     std::vector<uint32_t> return_and_parameter_types;
587     opt::analysis::Void void_type;
588     return_type_id = ir_context->get_type_mgr()->GetId(&void_type);
589     return_and_parameter_types.push_back(return_type_id);
590     for (auto id : region_input_ids) {
591       return_and_parameter_types.push_back(
592           ir_context->get_def_use_mgr()->GetDef(id)->type_id());
593     }
594     function_type_id =
595         fuzzerutil::FindFunctionType(ir_context, return_and_parameter_types);
596   }
597 
598   // If no existing function type was found, we need to create one.
599   if (function_type_id == 0) {
600     assert(
601         ((return_type_id == 0) == !region_output_ids.empty()) &&
602         "We should only have set the return type if there are no output ids.");
603     // If the region generates output ids, we need to make a struct with one
604     // field per output id.
605     if (!region_output_ids.empty()) {
606       opt::Instruction::OperandList struct_member_types;
607       for (uint32_t output_id : region_output_ids) {
608         auto output_id_type =
609             ir_context->get_def_use_mgr()->GetDef(output_id)->type_id();
610         if (ir_context->get_def_use_mgr()->GetDef(output_id_type)->opcode() ==
611             spv::Op::OpTypeVoid) {
612           // We cannot add a void field to a struct.  We instead use OpUndef to
613           // handle void output ids.
614           continue;
615         }
616         struct_member_types.push_back({SPV_OPERAND_TYPE_ID, {output_id_type}});
617       }
618       // Add a new struct type to the module.
619       ir_context->module()->AddType(MakeUnique<opt::Instruction>(
620           ir_context, spv::Op::OpTypeStruct, 0,
621           message_.new_function_struct_return_type_id(),
622           std::move(struct_member_types)));
623       // The return type for the function is the newly-created struct.
624       return_type_id = message_.new_function_struct_return_type_id();
625     }
626     assert(
627         return_type_id != 0 &&
628         "We should either have a void return type, or have created a struct.");
629 
630     // The region's input ids dictate the parameter types to the function.
631     opt::Instruction::OperandList function_type_operands;
632     function_type_operands.push_back({SPV_OPERAND_TYPE_ID, {return_type_id}});
633     for (auto id : region_input_ids) {
634       function_type_operands.push_back(
635           {SPV_OPERAND_TYPE_ID,
636            {ir_context->get_def_use_mgr()->GetDef(id)->type_id()}});
637     }
638     // Add a new function type to the module, and record that this is the type
639     // id for the new function.
640     ir_context->module()->AddType(MakeUnique<opt::Instruction>(
641         ir_context, spv::Op::OpTypeFunction, 0, message_.new_function_type_id(),
642         function_type_operands));
643     function_type_id = message_.new_function_type_id();
644   }
645 
646   // Create a new function with |message_.new_function_id| as the function id,
647   // and the return type and function type prepared above.
648   std::unique_ptr<opt::Function> outlined_function =
649       MakeUnique<opt::Function>(MakeUnique<opt::Instruction>(
650           ir_context, spv::Op::OpFunction, return_type_id,
651           message_.new_function_id(),
652           opt::Instruction::OperandList(
653               {{spv_operand_type_t ::SPV_OPERAND_TYPE_LITERAL_INTEGER,
654                 {uint32_t(spv::FunctionControlMask::MaskNone)}},
655                {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
656                 {function_type_id}}})));
657 
658   // Add one parameter to the function for each input id, using the fresh ids
659   // provided in |input_id_to_fresh_id_map|, or overflow ids if needed.
660   for (auto id : region_input_ids) {
661     uint32_t fresh_id = input_id_to_fresh_id_map.at(id);
662     outlined_function->AddParameter(MakeUnique<opt::Instruction>(
663         ir_context, spv::Op::OpFunctionParameter,
664         ir_context->get_def_use_mgr()->GetDef(id)->type_id(), fresh_id,
665         opt::Instruction::OperandList()));
666 
667     // Analyse the use of the new parameter instruction.
668     outlined_function->ForEachParam(
669         [fresh_id, ir_context](opt::Instruction* inst) {
670           if (inst->result_id() == fresh_id) {
671             ir_context->AnalyzeDefUse(inst);
672           }
673         });
674 
675     // If the input id is an irrelevant-valued variable, the same should be true
676     // of the corresponding parameter.
677     if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
678             id)) {
679       transformation_context->GetFactManager()
680           ->AddFactValueOfPointeeIsIrrelevant(input_id_to_fresh_id_map.at(id));
681     }
682   }
683 
684   return outlined_function;
685 }
686 
UpdateModuleIdBoundForFreshIds(opt::IRContext * ir_context,const std::map<uint32_t,uint32_t> & input_id_to_fresh_id_map,const std::map<uint32_t,uint32_t> & output_id_to_fresh_id_map) const687 void TransformationOutlineFunction::UpdateModuleIdBoundForFreshIds(
688     opt::IRContext* ir_context,
689     const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
690     const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const {
691   // Enlarge the module's id bound as needed to accommodate the various fresh
692   // ids associated with the transformation.
693   fuzzerutil::UpdateModuleIdBound(
694       ir_context, message_.new_function_struct_return_type_id());
695   fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_function_type_id());
696   fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_function_id());
697   fuzzerutil::UpdateModuleIdBound(ir_context,
698                                   message_.new_function_region_entry_block());
699   fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_caller_result_id());
700   fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_callee_result_id());
701 
702   for (auto& entry : input_id_to_fresh_id_map) {
703     fuzzerutil::UpdateModuleIdBound(ir_context, entry.second);
704   }
705 
706   for (auto& entry : output_id_to_fresh_id_map) {
707     fuzzerutil::UpdateModuleIdBound(ir_context, entry.second);
708   }
709 }
710 
RemapInputAndOutputIdsInRegion(opt::IRContext * ir_context,const opt::BasicBlock & original_region_exit_block,const std::set<opt::BasicBlock * > & region_blocks,const std::vector<uint32_t> & region_input_ids,const std::vector<uint32_t> & region_output_ids,const std::map<uint32_t,uint32_t> & input_id_to_fresh_id_map,const std::map<uint32_t,uint32_t> & output_id_to_fresh_id_map) const711 void TransformationOutlineFunction::RemapInputAndOutputIdsInRegion(
712     opt::IRContext* ir_context,
713     const opt::BasicBlock& original_region_exit_block,
714     const std::set<opt::BasicBlock*>& region_blocks,
715     const std::vector<uint32_t>& region_input_ids,
716     const std::vector<uint32_t>& region_output_ids,
717     const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
718     const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const {
719   // Change all uses of input ids inside the region to the corresponding fresh
720   // ids that will ultimately be parameters of the outlined function.
721   // This is done by considering each region input id in turn.
722   for (uint32_t id : region_input_ids) {
723     // We then consider each use of the input id.
724     ir_context->get_def_use_mgr()->ForEachUse(
725         id, [ir_context, id, &input_id_to_fresh_id_map, region_blocks](
726                 opt::Instruction* use, uint32_t operand_index) {
727           // Find the block in which this use of the input id occurs.
728           opt::BasicBlock* use_block = ir_context->get_instr_block(use);
729           // We want to rewrite the use id if its block occurs in the outlined
730           // region.
731           if (region_blocks.count(use_block) != 0) {
732             // Rewrite this use of the input id.
733             use->SetOperand(operand_index, {input_id_to_fresh_id_map.at(id)});
734           }
735         });
736   }
737 
738   // Change each definition of a region output id to define the corresponding
739   // fresh ids that will store intermediate value for the output ids.  Also
740   // change all uses of the output id located in the outlined region.
741   // This is done by considering each region output id in turn.
742   for (uint32_t id : region_output_ids) {
743     // First consider each use of the output id and update the relevant uses.
744     ir_context->get_def_use_mgr()->ForEachUse(
745         id, [ir_context, &original_region_exit_block, id,
746              &output_id_to_fresh_id_map,
747              region_blocks](opt::Instruction* use, uint32_t operand_index) {
748           // Find the block in which this use of the output id occurs.
749           auto use_block = ir_context->get_instr_block(use);
750           // We want to rewrite the use id if its block occurs in the outlined
751           // region, with one exception: the terminator of the exit block of
752           // the region is going to remain in the original function, so if the
753           // use appears in such a terminator instruction we leave it alone.
754           if (
755               // The block is in the region ...
756               region_blocks.count(use_block) != 0 &&
757               // ... and the use is not in the terminator instruction of the
758               // region's exit block.
759               !(use_block == &original_region_exit_block &&
760                 use->IsBlockTerminator())) {
761             // Rewrite this use of the output id.
762             use->SetOperand(operand_index, {output_id_to_fresh_id_map.at(id)});
763           }
764         });
765 
766     // Now change the instruction that defines the output id so that it instead
767     // defines the corresponding fresh id.  We do this after changing all the
768     // uses so that the definition of the original id is still registered when
769     // we analyse its uses.
770     ir_context->get_def_use_mgr()->GetDef(id)->SetResultId(
771         output_id_to_fresh_id_map.at(id));
772   }
773 }
774 
PopulateOutlinedFunction(const opt::BasicBlock & original_region_entry_block,const opt::BasicBlock & original_region_exit_block,const std::set<opt::BasicBlock * > & region_blocks,const std::vector<uint32_t> & region_output_ids,const std::map<uint32_t,uint32_t> & output_id_to_type_id,const std::map<uint32_t,uint32_t> & output_id_to_fresh_id_map,opt::IRContext * ir_context,opt::Function * outlined_function) const775 void TransformationOutlineFunction::PopulateOutlinedFunction(
776     const opt::BasicBlock& original_region_entry_block,
777     const opt::BasicBlock& original_region_exit_block,
778     const std::set<opt::BasicBlock*>& region_blocks,
779     const std::vector<uint32_t>& region_output_ids,
780     const std::map<uint32_t, uint32_t>& output_id_to_type_id,
781     const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map,
782     opt::IRContext* ir_context, opt::Function* outlined_function) const {
783   // When we create the exit block for the outlined region, we use this pointer
784   // to track of it so that we can manipulate it later.
785   opt::BasicBlock* outlined_region_exit_block = nullptr;
786 
787   // The region entry block in the new function is identical to the entry block
788   // of the region being outlined, except that it has
789   // |message_.new_function_region_entry_block| as its id.
790   std::unique_ptr<opt::BasicBlock> outlined_region_entry_block =
791       MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
792           ir_context, spv::Op::OpLabel, 0,
793           message_.new_function_region_entry_block(),
794           opt::Instruction::OperandList()));
795 
796   if (&original_region_entry_block == &original_region_exit_block) {
797     outlined_region_exit_block = outlined_region_entry_block.get();
798   }
799 
800   for (auto& inst : original_region_entry_block) {
801     outlined_region_entry_block->AddInstruction(
802         std::unique_ptr<opt::Instruction>(inst.Clone(ir_context)));
803   }
804   outlined_function->AddBasicBlock(std::move(outlined_region_entry_block));
805 
806   // We now go through the single-entry single-exit region defined by the entry
807   // and exit blocks, adding clones of all blocks to the new function.
808 
809   // Consider every block in the enclosing function.
810   auto enclosing_function = original_region_entry_block.GetParent();
811   for (auto block_it = enclosing_function->begin();
812        block_it != enclosing_function->end();) {
813     // Skip the region's entry block - we already dealt with it above.
814     if (region_blocks.count(&*block_it) == 0 ||
815         &*block_it == &original_region_entry_block) {
816       ++block_it;
817       continue;
818     }
819     // Clone the block so that it can be added to the new function.
820     auto cloned_block =
821         std::unique_ptr<opt::BasicBlock>(block_it->Clone(ir_context));
822 
823     // If this is the region's exit block, then the cloned block is the outlined
824     // region's exit block.
825     if (&*block_it == &original_region_exit_block) {
826       assert(outlined_region_exit_block == nullptr &&
827              "We should not yet have encountered the exit block.");
828       outlined_region_exit_block = cloned_block.get();
829     }
830 
831     // Redirect any OpPhi operands whose predecessors are the original region
832     // entry block to become the new function entry block.
833     cloned_block->ForEachPhiInst([this](opt::Instruction* phi_inst) {
834       for (uint32_t predecessor_index = 1;
835            predecessor_index < phi_inst->NumInOperands();
836            predecessor_index += 2) {
837         if (phi_inst->GetSingleWordInOperand(predecessor_index) ==
838             message_.entry_block()) {
839           phi_inst->SetInOperand(predecessor_index,
840                                  {message_.new_function_region_entry_block()});
841         }
842       }
843     });
844 
845     outlined_function->AddBasicBlock(std::move(cloned_block));
846     block_it = block_it.Erase();
847   }
848   assert(outlined_region_exit_block != nullptr &&
849          "We should have encountered the region's exit block when iterating "
850          "through the function");
851 
852   // We now need to adapt the exit block for the region - in the new function -
853   // so that it ends with a return.
854 
855   // We first eliminate the merge instruction (if any) and the terminator for
856   // the cloned exit block.
857   for (auto inst_it = outlined_region_exit_block->begin();
858        inst_it != outlined_region_exit_block->end();) {
859     if (inst_it->opcode() == spv::Op::OpLoopMerge ||
860         inst_it->opcode() == spv::Op::OpSelectionMerge) {
861       inst_it = inst_it.Erase();
862     } else if (inst_it->IsBlockTerminator()) {
863       inst_it = inst_it.Erase();
864     } else {
865       ++inst_it;
866     }
867   }
868 
869   // We now add either OpReturn or OpReturnValue as the cloned exit block's
870   // terminator.
871   if (region_output_ids.empty()) {
872     // The case where there are no region output ids is simple: we just add
873     // OpReturn.
874     outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
875         ir_context, spv::Op::OpReturn, 0, 0, opt::Instruction::OperandList()));
876   } else {
877     // In the case where there are output ids, we add an OpCompositeConstruct
878     // instruction to pack all the non-void output values into a struct, and
879     // then an OpReturnValue instruction to return this struct.
880     opt::Instruction::OperandList struct_member_operands;
881     for (uint32_t id : region_output_ids) {
882       if (ir_context->get_def_use_mgr()
883               ->GetDef(output_id_to_type_id.at(id))
884               ->opcode() != spv::Op::OpTypeVoid) {
885         struct_member_operands.push_back(
886             {SPV_OPERAND_TYPE_ID, {output_id_to_fresh_id_map.at(id)}});
887       }
888     }
889     outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
890         ir_context, spv::Op::OpCompositeConstruct,
891         message_.new_function_struct_return_type_id(),
892         message_.new_callee_result_id(), struct_member_operands));
893     outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
894         ir_context, spv::Op::OpReturnValue, 0, 0,
895         opt::Instruction::OperandList(
896             {{SPV_OPERAND_TYPE_ID, {message_.new_callee_result_id()}}})));
897   }
898 
899   outlined_function->SetFunctionEnd(
900       MakeUnique<opt::Instruction>(ir_context, spv::Op::OpFunctionEnd, 0, 0,
901                                    opt::Instruction::OperandList()));
902 }
903 
ShrinkOriginalRegion(opt::IRContext * ir_context,const std::set<opt::BasicBlock * > & region_blocks,const std::vector<uint32_t> & region_input_ids,const std::vector<uint32_t> & region_output_ids,const std::map<uint32_t,uint32_t> & output_id_to_type_id,uint32_t return_type_id,std::unique_ptr<opt::Instruction> cloned_exit_block_merge,std::unique_ptr<opt::Instruction> cloned_exit_block_terminator,opt::BasicBlock * original_region_entry_block) const904 void TransformationOutlineFunction::ShrinkOriginalRegion(
905     opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_blocks,
906     const std::vector<uint32_t>& region_input_ids,
907     const std::vector<uint32_t>& region_output_ids,
908     const std::map<uint32_t, uint32_t>& output_id_to_type_id,
909     uint32_t return_type_id,
910     std::unique_ptr<opt::Instruction> cloned_exit_block_merge,
911     std::unique_ptr<opt::Instruction> cloned_exit_block_terminator,
912     opt::BasicBlock* original_region_entry_block) const {
913   // Erase all blocks from the original function that are in the outlined
914   // region, except for the region's entry block.
915   //
916   // In the process, identify all references to the exit block of the region,
917   // as merge blocks, continue targets, or OpPhi predecessors, and rewrite them
918   // to refer to the region entry block (the single block to which we are
919   // shrinking the region).
920   auto enclosing_function = original_region_entry_block->GetParent();
921   for (auto block_it = enclosing_function->begin();
922        block_it != enclosing_function->end();) {
923     if (&*block_it == original_region_entry_block) {
924       ++block_it;
925     } else if (region_blocks.count(&*block_it) == 0) {
926       // The block is not in the region.  Check whether it has the last block
927       // of the region as an OpPhi predecessor, and if so change the
928       // predecessor to be the first block of the region (i.e. the block
929       // containing the call to what was outlined).
930       assert(block_it->MergeBlockIdIfAny() != message_.exit_block() &&
931              "Outlined region must not end with a merge block");
932       assert(block_it->ContinueBlockIdIfAny() != message_.exit_block() &&
933              "Outlined region must not end with a continue target");
934       block_it->ForEachPhiInst([this](opt::Instruction* phi_inst) {
935         for (uint32_t predecessor_index = 1;
936              predecessor_index < phi_inst->NumInOperands();
937              predecessor_index += 2) {
938           if (phi_inst->GetSingleWordInOperand(predecessor_index) ==
939               message_.exit_block()) {
940             phi_inst->SetInOperand(predecessor_index, {message_.entry_block()});
941           }
942         }
943       });
944       ++block_it;
945     } else {
946       // The block is in the region and is not the region's entry block: kill
947       // it.
948       block_it = block_it.Erase();
949     }
950   }
951 
952   // Now erase all instructions from the region's entry block, as they have
953   // been outlined.
954   for (auto inst_it = original_region_entry_block->begin();
955        inst_it != original_region_entry_block->end();) {
956     inst_it = inst_it.Erase();
957   }
958 
959   // Now we add a call to the outlined function to the region's entry block.
960   opt::Instruction::OperandList function_call_operands;
961   function_call_operands.push_back(
962       {SPV_OPERAND_TYPE_ID, {message_.new_function_id()}});
963   // The function parameters are the region input ids.
964   for (auto input_id : region_input_ids) {
965     function_call_operands.push_back({SPV_OPERAND_TYPE_ID, {input_id}});
966   }
967 
968   original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
969       ir_context, spv::Op::OpFunctionCall, return_type_id,
970       message_.new_caller_result_id(), function_call_operands));
971 
972   // If there are output ids, the function call will return a struct.  For each
973   // output id, we add an extract operation to pull the appropriate struct
974   // member out into an output id.  The exception is for output ids with void
975   // type.  There are no struct entries for these, so we use an OpUndef of void
976   // type instead.
977   uint32_t struct_member_index = 0;
978   for (uint32_t output_id : region_output_ids) {
979     uint32_t output_type_id = output_id_to_type_id.at(output_id);
980     if (ir_context->get_def_use_mgr()->GetDef(output_type_id)->opcode() ==
981         spv::Op::OpTypeVoid) {
982       original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
983           ir_context, spv::Op::OpUndef, output_type_id, output_id,
984           opt::Instruction::OperandList()));
985       // struct_member_index is not incremented since there was no struct member
986       // associated with this void-typed output id.
987     } else {
988       original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
989           ir_context, spv::Op::OpCompositeExtract, output_type_id, output_id,
990           opt::Instruction::OperandList(
991               {{SPV_OPERAND_TYPE_ID, {message_.new_caller_result_id()}},
992                {SPV_OPERAND_TYPE_LITERAL_INTEGER, {struct_member_index}}})));
993       struct_member_index++;
994     }
995   }
996 
997   // Finally, we terminate the block with the merge instruction (if any) that
998   // used to belong to the region's exit block, and the terminator that used
999   // to belong to the region's exit block.
1000   if (cloned_exit_block_merge != nullptr) {
1001     original_region_entry_block->AddInstruction(
1002         std::move(cloned_exit_block_merge));
1003   }
1004   original_region_entry_block->AddInstruction(
1005       std::move(cloned_exit_block_terminator));
1006 }
1007 
GetFreshIds() const1008 std::unordered_set<uint32_t> TransformationOutlineFunction::GetFreshIds()
1009     const {
1010   std::unordered_set<uint32_t> result = {
1011       message_.new_function_struct_return_type_id(),
1012       message_.new_function_type_id(),
1013       message_.new_function_id(),
1014       message_.new_function_region_entry_block(),
1015       message_.new_caller_result_id(),
1016       message_.new_callee_result_id()};
1017   for (auto& pair : message_.input_id_to_fresh_id()) {
1018     result.insert(pair.second());
1019   }
1020   for (auto& pair : message_.output_id_to_fresh_id()) {
1021     result.insert(pair.second());
1022   }
1023   return result;
1024 }
1025 
1026 }  // namespace fuzz
1027 }  // namespace spvtools
1028