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