• 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/fuzzer_pass.h"
16 
17 #include <set>
18 
19 #include "source/fuzz/fuzzer_util.h"
20 #include "source/fuzz/id_use_descriptor.h"
21 #include "source/fuzz/instruction_descriptor.h"
22 #include "source/fuzz/transformation_add_constant_boolean.h"
23 #include "source/fuzz/transformation_add_constant_composite.h"
24 #include "source/fuzz/transformation_add_constant_null.h"
25 #include "source/fuzz/transformation_add_constant_scalar.h"
26 #include "source/fuzz/transformation_add_global_undef.h"
27 #include "source/fuzz/transformation_add_global_variable.h"
28 #include "source/fuzz/transformation_add_local_variable.h"
29 #include "source/fuzz/transformation_add_loop_preheader.h"
30 #include "source/fuzz/transformation_add_type_boolean.h"
31 #include "source/fuzz/transformation_add_type_float.h"
32 #include "source/fuzz/transformation_add_type_function.h"
33 #include "source/fuzz/transformation_add_type_int.h"
34 #include "source/fuzz/transformation_add_type_matrix.h"
35 #include "source/fuzz/transformation_add_type_pointer.h"
36 #include "source/fuzz/transformation_add_type_struct.h"
37 #include "source/fuzz/transformation_add_type_vector.h"
38 #include "source/fuzz/transformation_split_block.h"
39 
40 namespace spvtools {
41 namespace fuzz {
42 
FuzzerPass(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations,bool ignore_inapplicable_transformations)43 FuzzerPass::FuzzerPass(opt::IRContext* ir_context,
44                        TransformationContext* transformation_context,
45                        FuzzerContext* fuzzer_context,
46                        protobufs::TransformationSequence* transformations,
47                        bool ignore_inapplicable_transformations)
48     : ir_context_(ir_context),
49       transformation_context_(transformation_context),
50       fuzzer_context_(fuzzer_context),
51       transformations_(transformations),
52       ignore_inapplicable_transformations_(
53           ignore_inapplicable_transformations) {}
54 
55 FuzzerPass::~FuzzerPass() = default;
56 
FindAvailableInstructions(opt::Function * function,opt::BasicBlock * block,const opt::BasicBlock::iterator & inst_it,std::function<bool (opt::IRContext *,opt::Instruction *)> instruction_is_relevant) const57 std::vector<opt::Instruction*> FuzzerPass::FindAvailableInstructions(
58     opt::Function* function, opt::BasicBlock* block,
59     const opt::BasicBlock::iterator& inst_it,
60     std::function<bool(opt::IRContext*, opt::Instruction*)>
61         instruction_is_relevant) const {
62   // TODO(afd) The following is (relatively) simple, but may end up being
63   //  prohibitively inefficient, as it walks the whole dominator tree for
64   //  every instruction that is considered.
65 
66   std::vector<opt::Instruction*> result;
67   // Consider all global declarations
68   for (auto& global : GetIRContext()->module()->types_values()) {
69     if (instruction_is_relevant(GetIRContext(), &global)) {
70       result.push_back(&global);
71     }
72   }
73 
74   // Consider all function parameters
75   function->ForEachParam(
76       [this, &instruction_is_relevant, &result](opt::Instruction* param) {
77         if (instruction_is_relevant(GetIRContext(), param)) {
78           result.push_back(param);
79         }
80       });
81 
82   // Consider all previous instructions in this block
83   for (auto prev_inst_it = block->begin(); prev_inst_it != inst_it;
84        ++prev_inst_it) {
85     if (instruction_is_relevant(GetIRContext(), &*prev_inst_it)) {
86       result.push_back(&*prev_inst_it);
87     }
88   }
89 
90   // Walk the dominator tree to consider all instructions from dominating
91   // blocks
92   auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function);
93   for (auto next_dominator = dominator_analysis->ImmediateDominator(block);
94        next_dominator != nullptr;
95        next_dominator =
96            dominator_analysis->ImmediateDominator(next_dominator)) {
97     for (auto& dominating_inst : *next_dominator) {
98       if (instruction_is_relevant(GetIRContext(), &dominating_inst)) {
99         result.push_back(&dominating_inst);
100       }
101     }
102   }
103   return result;
104 }
105 
ForEachInstructionWithInstructionDescriptor(opt::Function * function,std::function<void (opt::BasicBlock * block,opt::BasicBlock::iterator inst_it,const protobufs::InstructionDescriptor & instruction_descriptor)> action)106 void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
107     opt::Function* function,
108     std::function<
109         void(opt::BasicBlock* block, opt::BasicBlock::iterator inst_it,
110              const protobufs::InstructionDescriptor& instruction_descriptor)>
111         action) {
112   // Consider only reachable blocks. We do this in a separate loop to avoid
113   // recomputing the dominator analysis every time |action| changes the
114   // module.
115   std::vector<opt::BasicBlock*> reachable_blocks;
116 
117   for (auto& block : *function) {
118     if (GetIRContext()->IsReachable(block)) {
119       reachable_blocks.push_back(&block);
120     }
121   }
122 
123   for (auto* block : reachable_blocks) {
124     // We now consider every instruction in the block, randomly deciding
125     // whether to apply a transformation before it.
126 
127     // In order for transformations to insert new instructions, they need to
128     // be able to identify the instruction to insert before.  We describe an
129     // instruction via its opcode, 'opc', a base instruction 'base' that has a
130     // result id, and the number of instructions with opcode 'opc' that we
131     // should skip when searching from 'base' for the desired instruction.
132     // (An instruction that has a result id is represented by its own opcode,
133     // itself as 'base', and a skip-count of 0.)
134     std::vector<std::tuple<uint32_t, SpvOp, uint32_t>> base_opcode_skip_triples;
135 
136     // The initial base instruction is the block label.
137     uint32_t base = block->id();
138 
139     // Counts the number of times we have seen each opcode since we reset the
140     // base instruction.
141     std::map<SpvOp, uint32_t> skip_count;
142 
143     // Consider every instruction in the block.  The label is excluded: it is
144     // only necessary to consider it as a base in case the first instruction
145     // in the block does not have a result id.
146     for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) {
147       if (inst_it->HasResultId()) {
148         // In the case that the instruction has a result id, we use the
149         // instruction as its own base, and clear the skip counts we have
150         // collected.
151         base = inst_it->result_id();
152         skip_count.clear();
153       }
154       const SpvOp opcode = inst_it->opcode();
155 
156       // Invoke the provided function, which might apply a transformation.
157       action(block, inst_it,
158              MakeInstructionDescriptor(
159                  base, opcode,
160                  skip_count.count(opcode) ? skip_count.at(opcode) : 0));
161 
162       if (!inst_it->HasResultId()) {
163         skip_count[opcode] =
164             skip_count.count(opcode) ? skip_count.at(opcode) + 1 : 1;
165       }
166     }
167   }
168 }
169 
ForEachInstructionWithInstructionDescriptor(std::function<void (opt::Function * function,opt::BasicBlock * block,opt::BasicBlock::iterator inst_it,const protobufs::InstructionDescriptor & instruction_descriptor)> action)170 void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
171     std::function<
172         void(opt::Function* function, opt::BasicBlock* block,
173              opt::BasicBlock::iterator inst_it,
174              const protobufs::InstructionDescriptor& instruction_descriptor)>
175         action) {
176   // Consider every block in every function.
177   for (auto& function : *GetIRContext()->module()) {
178     ForEachInstructionWithInstructionDescriptor(
179         &function,
180         [&action, &function](
181             opt::BasicBlock* block, opt::BasicBlock::iterator inst_it,
182             const protobufs::InstructionDescriptor& instruction_descriptor) {
183           action(&function, block, inst_it, instruction_descriptor);
184         });
185   }
186 }
187 
ApplyTransformation(const Transformation & transformation)188 void FuzzerPass::ApplyTransformation(const Transformation& transformation) {
189   if (ignore_inapplicable_transformations_) {
190     // If an applicable-by-construction transformation turns out to be
191     // inapplicable, this is a bug in the fuzzer. However, when deploying the
192     // fuzzer at scale for finding bugs in SPIR-V processing tools it is
193     // desirable to silently ignore such bugs. This code path caters for that
194     // scenario.
195     if (!transformation.IsApplicable(GetIRContext(),
196                                      *GetTransformationContext())) {
197       return;
198     }
199   } else {
200     // This code path caters for debugging bugs in the fuzzer, where an
201     // applicable-by-construction transformation turns out to be inapplicable.
202     assert(transformation.IsApplicable(GetIRContext(),
203                                        *GetTransformationContext()) &&
204            "Transformation should be applicable by construction.");
205   }
206   transformation.Apply(GetIRContext(), GetTransformationContext());
207   auto transformation_message = transformation.ToMessage();
208   assert(transformation_message.transformation_case() !=
209              protobufs::Transformation::TRANSFORMATION_NOT_SET &&
210          "Bad transformation.");
211   *GetTransformations()->add_transformation() =
212       std::move(transformation_message);
213 }
214 
MaybeApplyTransformation(const Transformation & transformation)215 bool FuzzerPass::MaybeApplyTransformation(
216     const Transformation& transformation) {
217   if (transformation.IsApplicable(GetIRContext(),
218                                   *GetTransformationContext())) {
219     transformation.Apply(GetIRContext(), GetTransformationContext());
220     auto transformation_message = transformation.ToMessage();
221     assert(transformation_message.transformation_case() !=
222                protobufs::Transformation::TRANSFORMATION_NOT_SET &&
223            "Bad transformation.");
224     *GetTransformations()->add_transformation() =
225         std::move(transformation_message);
226     return true;
227   }
228   return false;
229 }
230 
FindOrCreateBoolType()231 uint32_t FuzzerPass::FindOrCreateBoolType() {
232   if (auto existing_id = fuzzerutil::MaybeGetBoolType(GetIRContext())) {
233     return existing_id;
234   }
235   auto result = GetFuzzerContext()->GetFreshId();
236   ApplyTransformation(TransformationAddTypeBoolean(result));
237   return result;
238 }
239 
FindOrCreateIntegerType(uint32_t width,bool is_signed)240 uint32_t FuzzerPass::FindOrCreateIntegerType(uint32_t width, bool is_signed) {
241   opt::analysis::Integer int_type(width, is_signed);
242   auto existing_id = GetIRContext()->get_type_mgr()->GetId(&int_type);
243   if (existing_id) {
244     return existing_id;
245   }
246   auto result = GetFuzzerContext()->GetFreshId();
247   ApplyTransformation(TransformationAddTypeInt(result, width, is_signed));
248   return result;
249 }
250 
FindOrCreateFloatType(uint32_t width)251 uint32_t FuzzerPass::FindOrCreateFloatType(uint32_t width) {
252   opt::analysis::Float float_type(width);
253   auto existing_id = GetIRContext()->get_type_mgr()->GetId(&float_type);
254   if (existing_id) {
255     return existing_id;
256   }
257   auto result = GetFuzzerContext()->GetFreshId();
258   ApplyTransformation(TransformationAddTypeFloat(result, width));
259   return result;
260 }
261 
FindOrCreateFunctionType(uint32_t return_type_id,const std::vector<uint32_t> & argument_id)262 uint32_t FuzzerPass::FindOrCreateFunctionType(
263     uint32_t return_type_id, const std::vector<uint32_t>& argument_id) {
264   // FindFunctionType has a single argument for OpTypeFunction operands
265   // so we will have to copy them all in this vector
266   std::vector<uint32_t> type_ids(argument_id.size() + 1);
267   type_ids[0] = return_type_id;
268   std::copy(argument_id.begin(), argument_id.end(), type_ids.begin() + 1);
269 
270   // Check if type exists
271   auto existing_id = fuzzerutil::FindFunctionType(GetIRContext(), type_ids);
272   if (existing_id) {
273     return existing_id;
274   }
275 
276   auto result = GetFuzzerContext()->GetFreshId();
277   ApplyTransformation(
278       TransformationAddTypeFunction(result, return_type_id, argument_id));
279   return result;
280 }
281 
FindOrCreateVectorType(uint32_t component_type_id,uint32_t component_count)282 uint32_t FuzzerPass::FindOrCreateVectorType(uint32_t component_type_id,
283                                             uint32_t component_count) {
284   assert(component_count >= 2 && component_count <= 4 &&
285          "Precondition: component count must be in range [2, 4].");
286   opt::analysis::Type* component_type =
287       GetIRContext()->get_type_mgr()->GetType(component_type_id);
288   assert(component_type && "Precondition: the component type must exist.");
289   opt::analysis::Vector vector_type(component_type, component_count);
290   auto existing_id = GetIRContext()->get_type_mgr()->GetId(&vector_type);
291   if (existing_id) {
292     return existing_id;
293   }
294   auto result = GetFuzzerContext()->GetFreshId();
295   ApplyTransformation(
296       TransformationAddTypeVector(result, component_type_id, component_count));
297   return result;
298 }
299 
FindOrCreateMatrixType(uint32_t column_count,uint32_t row_count)300 uint32_t FuzzerPass::FindOrCreateMatrixType(uint32_t column_count,
301                                             uint32_t row_count) {
302   assert(column_count >= 2 && column_count <= 4 &&
303          "Precondition: column count must be in range [2, 4].");
304   assert(row_count >= 2 && row_count <= 4 &&
305          "Precondition: row count must be in range [2, 4].");
306   uint32_t column_type_id =
307       FindOrCreateVectorType(FindOrCreateFloatType(32), row_count);
308   opt::analysis::Type* column_type =
309       GetIRContext()->get_type_mgr()->GetType(column_type_id);
310   opt::analysis::Matrix matrix_type(column_type, column_count);
311   auto existing_id = GetIRContext()->get_type_mgr()->GetId(&matrix_type);
312   if (existing_id) {
313     return existing_id;
314   }
315   auto result = GetFuzzerContext()->GetFreshId();
316   ApplyTransformation(
317       TransformationAddTypeMatrix(result, column_type_id, column_count));
318   return result;
319 }
320 
FindOrCreateStructType(const std::vector<uint32_t> & component_type_ids)321 uint32_t FuzzerPass::FindOrCreateStructType(
322     const std::vector<uint32_t>& component_type_ids) {
323   if (auto existing_id =
324           fuzzerutil::MaybeGetStructType(GetIRContext(), component_type_ids)) {
325     return existing_id;
326   }
327   auto new_id = GetFuzzerContext()->GetFreshId();
328   ApplyTransformation(TransformationAddTypeStruct(new_id, component_type_ids));
329   return new_id;
330 }
331 
FindOrCreatePointerType(uint32_t base_type_id,SpvStorageClass storage_class)332 uint32_t FuzzerPass::FindOrCreatePointerType(uint32_t base_type_id,
333                                              SpvStorageClass storage_class) {
334   // We do not use the type manager here, due to problems related to isomorphic
335   // but distinct structs not being regarded as different.
336   auto existing_id = fuzzerutil::MaybeGetPointerType(
337       GetIRContext(), base_type_id, storage_class);
338   if (existing_id) {
339     return existing_id;
340   }
341   auto result = GetFuzzerContext()->GetFreshId();
342   ApplyTransformation(
343       TransformationAddTypePointer(result, storage_class, base_type_id));
344   return result;
345 }
346 
FindOrCreatePointerToIntegerType(uint32_t width,bool is_signed,SpvStorageClass storage_class)347 uint32_t FuzzerPass::FindOrCreatePointerToIntegerType(
348     uint32_t width, bool is_signed, SpvStorageClass storage_class) {
349   return FindOrCreatePointerType(FindOrCreateIntegerType(width, is_signed),
350                                  storage_class);
351 }
352 
FindOrCreateIntegerConstant(const std::vector<uint32_t> & words,uint32_t width,bool is_signed,bool is_irrelevant)353 uint32_t FuzzerPass::FindOrCreateIntegerConstant(
354     const std::vector<uint32_t>& words, uint32_t width, bool is_signed,
355     bool is_irrelevant) {
356   auto int_type_id = FindOrCreateIntegerType(width, is_signed);
357   if (auto constant_id = fuzzerutil::MaybeGetScalarConstant(
358           GetIRContext(), *GetTransformationContext(), words, int_type_id,
359           is_irrelevant)) {
360     return constant_id;
361   }
362   auto result = GetFuzzerContext()->GetFreshId();
363   ApplyTransformation(TransformationAddConstantScalar(result, int_type_id,
364                                                       words, is_irrelevant));
365   return result;
366 }
367 
FindOrCreateFloatConstant(const std::vector<uint32_t> & words,uint32_t width,bool is_irrelevant)368 uint32_t FuzzerPass::FindOrCreateFloatConstant(
369     const std::vector<uint32_t>& words, uint32_t width, bool is_irrelevant) {
370   auto float_type_id = FindOrCreateFloatType(width);
371   if (auto constant_id = fuzzerutil::MaybeGetScalarConstant(
372           GetIRContext(), *GetTransformationContext(), words, float_type_id,
373           is_irrelevant)) {
374     return constant_id;
375   }
376   auto result = GetFuzzerContext()->GetFreshId();
377   ApplyTransformation(TransformationAddConstantScalar(result, float_type_id,
378                                                       words, is_irrelevant));
379   return result;
380 }
381 
FindOrCreateBoolConstant(bool value,bool is_irrelevant)382 uint32_t FuzzerPass::FindOrCreateBoolConstant(bool value, bool is_irrelevant) {
383   auto bool_type_id = FindOrCreateBoolType();
384   if (auto constant_id = fuzzerutil::MaybeGetScalarConstant(
385           GetIRContext(), *GetTransformationContext(), {value ? 1u : 0u},
386           bool_type_id, is_irrelevant)) {
387     return constant_id;
388   }
389   auto result = GetFuzzerContext()->GetFreshId();
390   ApplyTransformation(
391       TransformationAddConstantBoolean(result, value, is_irrelevant));
392   return result;
393 }
394 
FindOrCreateConstant(const std::vector<uint32_t> & words,uint32_t type_id,bool is_irrelevant)395 uint32_t FuzzerPass::FindOrCreateConstant(const std::vector<uint32_t>& words,
396                                           uint32_t type_id,
397                                           bool is_irrelevant) {
398   assert(type_id && "Constant's type id can't be 0.");
399 
400   const auto* type = GetIRContext()->get_type_mgr()->GetType(type_id);
401   assert(type && "Type does not exist.");
402 
403   if (type->AsBool()) {
404     assert(words.size() == 1);
405     return FindOrCreateBoolConstant(words[0], is_irrelevant);
406   } else if (const auto* integer = type->AsInteger()) {
407     return FindOrCreateIntegerConstant(words, integer->width(),
408                                        integer->IsSigned(), is_irrelevant);
409   } else if (const auto* floating = type->AsFloat()) {
410     return FindOrCreateFloatConstant(words, floating->width(), is_irrelevant);
411   }
412 
413   // This assertion will fail in debug build but not in release build
414   // so we return 0 to make compiler happy.
415   assert(false && "Constant type is not supported");
416   return 0;
417 }
418 
FindOrCreateCompositeConstant(const std::vector<uint32_t> & component_ids,uint32_t type_id,bool is_irrelevant)419 uint32_t FuzzerPass::FindOrCreateCompositeConstant(
420     const std::vector<uint32_t>& component_ids, uint32_t type_id,
421     bool is_irrelevant) {
422   if (auto existing_constant = fuzzerutil::MaybeGetCompositeConstant(
423           GetIRContext(), *GetTransformationContext(), component_ids, type_id,
424           is_irrelevant)) {
425     return existing_constant;
426   }
427   uint32_t result = GetFuzzerContext()->GetFreshId();
428   ApplyTransformation(TransformationAddConstantComposite(
429       result, type_id, component_ids, is_irrelevant));
430   return result;
431 }
432 
FindOrCreateGlobalUndef(uint32_t type_id)433 uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) {
434   for (auto& inst : GetIRContext()->types_values()) {
435     if (inst.opcode() == SpvOpUndef && inst.type_id() == type_id) {
436       return inst.result_id();
437     }
438   }
439   auto result = GetFuzzerContext()->GetFreshId();
440   ApplyTransformation(TransformationAddGlobalUndef(result, type_id));
441   return result;
442 }
443 
FindOrCreateNullConstant(uint32_t type_id)444 uint32_t FuzzerPass::FindOrCreateNullConstant(uint32_t type_id) {
445   // Find existing declaration
446   opt::analysis::NullConstant null_constant(
447       GetIRContext()->get_type_mgr()->GetType(type_id));
448   auto existing_constant =
449       GetIRContext()->get_constant_mgr()->FindConstant(&null_constant);
450 
451   // Return if found
452   if (existing_constant) {
453     return GetIRContext()
454         ->get_constant_mgr()
455         ->GetDefiningInstruction(existing_constant)
456         ->result_id();
457   }
458 
459   // Create new if not found
460   auto result = GetFuzzerContext()->GetFreshId();
461   ApplyTransformation(TransformationAddConstantNull(result, type_id));
462   return result;
463 }
464 
465 std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
GetAvailableBasicTypesAndPointers(SpvStorageClass storage_class) const466 FuzzerPass::GetAvailableBasicTypesAndPointers(
467     SpvStorageClass storage_class) const {
468   // Records all of the basic types available in the module.
469   std::set<uint32_t> basic_types;
470 
471   // For each basic type, records all the associated pointer types that target
472   // the basic type and that have |storage_class| as their storage class.
473   std::map<uint32_t, std::vector<uint32_t>> basic_type_to_pointers;
474 
475   for (auto& inst : GetIRContext()->types_values()) {
476     // For each basic type that we come across, record type, and the fact that
477     // we cannot yet have seen any pointers that use the basic type as its
478     // pointee type.
479     //
480     // For pointer types with basic pointee types, associate the pointer type
481     // with the basic type.
482     switch (inst.opcode()) {
483       case SpvOpTypeBool:
484       case SpvOpTypeFloat:
485       case SpvOpTypeInt:
486       case SpvOpTypeMatrix:
487       case SpvOpTypeVector:
488         // These are all basic types.
489         basic_types.insert(inst.result_id());
490         basic_type_to_pointers.insert({inst.result_id(), {}});
491         break;
492       case SpvOpTypeArray:
493         // An array type is basic if its base type is basic.
494         if (basic_types.count(inst.GetSingleWordInOperand(0))) {
495           basic_types.insert(inst.result_id());
496           basic_type_to_pointers.insert({inst.result_id(), {}});
497         }
498         break;
499       case SpvOpTypeStruct: {
500         // A struct type is basic if it does not have the Block/BufferBlock
501         // decoration, and if all of its members are basic.
502         if (!fuzzerutil::HasBlockOrBufferBlockDecoration(GetIRContext(),
503                                                          inst.result_id())) {
504           bool all_members_are_basic_types = true;
505           for (uint32_t i = 0; i < inst.NumInOperands(); i++) {
506             if (!basic_types.count(inst.GetSingleWordInOperand(i))) {
507               all_members_are_basic_types = false;
508               break;
509             }
510           }
511           if (all_members_are_basic_types) {
512             basic_types.insert(inst.result_id());
513             basic_type_to_pointers.insert({inst.result_id(), {}});
514           }
515         }
516         break;
517       }
518       case SpvOpTypePointer: {
519         // We are interested in the pointer if its pointee type is basic and it
520         // has the right storage class.
521         auto pointee_type = inst.GetSingleWordInOperand(1);
522         if (inst.GetSingleWordInOperand(0) == storage_class &&
523             basic_types.count(pointee_type)) {
524           // The pointer has the desired storage class, and its pointee type is
525           // a basic type, so we are interested in it.  Associate it with its
526           // basic type.
527           basic_type_to_pointers.at(pointee_type).push_back(inst.result_id());
528         }
529         break;
530       }
531       default:
532         break;
533     }
534   }
535   return {{basic_types.begin(), basic_types.end()}, basic_type_to_pointers};
536 }
537 
FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id,bool is_irrelevant)538 uint32_t FuzzerPass::FindOrCreateZeroConstant(
539     uint32_t scalar_or_composite_type_id, bool is_irrelevant) {
540   auto type_instruction =
541       GetIRContext()->get_def_use_mgr()->GetDef(scalar_or_composite_type_id);
542   assert(type_instruction && "The type instruction must exist.");
543   switch (type_instruction->opcode()) {
544     case SpvOpTypeBool:
545       return FindOrCreateBoolConstant(false, is_irrelevant);
546     case SpvOpTypeFloat: {
547       auto width = type_instruction->GetSingleWordInOperand(0);
548       auto num_words = (width + 32 - 1) / 32;
549       return FindOrCreateFloatConstant(std::vector<uint32_t>(num_words, 0),
550                                        width, is_irrelevant);
551     }
552     case SpvOpTypeInt: {
553       auto width = type_instruction->GetSingleWordInOperand(0);
554       auto num_words = (width + 32 - 1) / 32;
555       return FindOrCreateIntegerConstant(
556           std::vector<uint32_t>(num_words, 0), width,
557           type_instruction->GetSingleWordInOperand(1), is_irrelevant);
558     }
559     case SpvOpTypeArray: {
560       auto component_type_id = type_instruction->GetSingleWordInOperand(0);
561       auto num_components =
562           fuzzerutil::GetArraySize(*type_instruction, GetIRContext());
563       return FindOrCreateCompositeConstant(
564           std::vector<uint32_t>(
565               num_components,
566               FindOrCreateZeroConstant(component_type_id, is_irrelevant)),
567           scalar_or_composite_type_id, is_irrelevant);
568     }
569     case SpvOpTypeMatrix:
570     case SpvOpTypeVector: {
571       auto component_type_id = type_instruction->GetSingleWordInOperand(0);
572       auto num_components = type_instruction->GetSingleWordInOperand(1);
573       return FindOrCreateCompositeConstant(
574           std::vector<uint32_t>(
575               num_components,
576               FindOrCreateZeroConstant(component_type_id, is_irrelevant)),
577           scalar_or_composite_type_id, is_irrelevant);
578     }
579     case SpvOpTypeStruct: {
580       assert(!fuzzerutil::HasBlockOrBufferBlockDecoration(
581                  GetIRContext(), scalar_or_composite_type_id) &&
582              "We do not construct constants of struct types decorated with "
583              "Block or BufferBlock.");
584       std::vector<uint32_t> field_zero_ids;
585       for (uint32_t index = 0; index < type_instruction->NumInOperands();
586            index++) {
587         field_zero_ids.push_back(FindOrCreateZeroConstant(
588             type_instruction->GetSingleWordInOperand(index), is_irrelevant));
589       }
590       return FindOrCreateCompositeConstant(
591           field_zero_ids, scalar_or_composite_type_id, is_irrelevant);
592     }
593     default:
594       assert(false && "Unknown type.");
595       return 0;
596   }
597 }
598 
MaybeAddUseToReplace(opt::Instruction * use_inst,uint32_t use_index,uint32_t replacement_id,std::vector<std::pair<protobufs::IdUseDescriptor,uint32_t>> * uses_to_replace)599 void FuzzerPass::MaybeAddUseToReplace(
600     opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
601     std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
602         uses_to_replace) {
603   // Only consider this use if it is in a block
604   if (!GetIRContext()->get_instr_block(use_inst)) {
605     return;
606   }
607 
608   // Get the index of the operand restricted to input operands.
609   uint32_t in_operand_index =
610       fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
611   auto id_use_descriptor =
612       MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, in_operand_index);
613   uses_to_replace->emplace_back(
614       std::make_pair(id_use_descriptor, replacement_id));
615 }
616 
GetOrCreateSimpleLoopPreheader(uint32_t header_id)617 opt::BasicBlock* FuzzerPass::GetOrCreateSimpleLoopPreheader(
618     uint32_t header_id) {
619   auto header_block = fuzzerutil::MaybeFindBlock(GetIRContext(), header_id);
620 
621   assert(header_block && header_block->IsLoopHeader() &&
622          "|header_id| should be the label id of a loop header");
623 
624   auto predecessors = GetIRContext()->cfg()->preds(header_id);
625 
626   assert(predecessors.size() >= 2 &&
627          "The block |header_id| should be reachable.");
628 
629   auto function = header_block->GetParent();
630 
631   if (predecessors.size() == 2) {
632     // The header has a single out-of-loop predecessor, which could be a
633     // preheader.
634 
635     opt::BasicBlock* maybe_preheader;
636 
637     if (GetIRContext()->GetDominatorAnalysis(function)->Dominates(
638             header_id, predecessors[0])) {
639       // The first predecessor is the back-edge block, because the header
640       // dominates it, so the second one is out of the loop.
641       maybe_preheader = &*function->FindBlock(predecessors[1]);
642     } else {
643       // The first predecessor is out of the loop.
644       maybe_preheader = &*function->FindBlock(predecessors[0]);
645     }
646 
647     // |maybe_preheader| is a preheader if it branches unconditionally to
648     // the header. We also require it not to be a loop header.
649     if (maybe_preheader->terminator()->opcode() == SpvOpBranch &&
650         !maybe_preheader->IsLoopHeader()) {
651       return maybe_preheader;
652     }
653   }
654 
655   // We need to add a preheader.
656 
657   // Get a fresh id for the preheader.
658   uint32_t preheader_id = GetFuzzerContext()->GetFreshId();
659 
660   // Get a fresh id for each OpPhi instruction, if there is more than one
661   // out-of-loop predecessor.
662   std::vector<uint32_t> phi_ids;
663   if (predecessors.size() > 2) {
664     header_block->ForEachPhiInst(
665         [this, &phi_ids](opt::Instruction* /* unused */) {
666           phi_ids.push_back(GetFuzzerContext()->GetFreshId());
667         });
668   }
669 
670   // Add the preheader.
671   ApplyTransformation(
672       TransformationAddLoopPreheader(header_id, preheader_id, phi_ids));
673 
674   // Make the newly-created preheader the new entry block.
675   return &*function->FindBlock(preheader_id);
676 }
677 
SplitBlockAfterOpPhiOrOpVariable(uint32_t block_id)678 opt::BasicBlock* FuzzerPass::SplitBlockAfterOpPhiOrOpVariable(
679     uint32_t block_id) {
680   auto block = fuzzerutil::MaybeFindBlock(GetIRContext(), block_id);
681   assert(block && "|block_id| must be a block label");
682   assert(!block->IsLoopHeader() && "|block_id| cannot be a loop header");
683 
684   // Find the first non-OpPhi and non-OpVariable instruction.
685   auto non_phi_or_var_inst = &*block->begin();
686   while (non_phi_or_var_inst->opcode() == SpvOpPhi ||
687          non_phi_or_var_inst->opcode() == SpvOpVariable) {
688     non_phi_or_var_inst = non_phi_or_var_inst->NextNode();
689   }
690 
691   // Split the block.
692   uint32_t new_block_id = GetFuzzerContext()->GetFreshId();
693   ApplyTransformation(TransformationSplitBlock(
694       MakeInstructionDescriptor(GetIRContext(), non_phi_or_var_inst),
695       new_block_id));
696 
697   // We need to return the newly-created block.
698   return &*block->GetParent()->FindBlock(new_block_id);
699 }
700 
FindOrCreateLocalVariable(uint32_t pointer_type_id,uint32_t function_id,bool pointee_value_is_irrelevant)701 uint32_t FuzzerPass::FindOrCreateLocalVariable(
702     uint32_t pointer_type_id, uint32_t function_id,
703     bool pointee_value_is_irrelevant) {
704   auto pointer_type = GetIRContext()->get_type_mgr()->GetType(pointer_type_id);
705   // No unused variables in release mode.
706   (void)pointer_type;
707   assert(pointer_type && pointer_type->AsPointer() &&
708          pointer_type->AsPointer()->storage_class() ==
709              SpvStorageClassFunction &&
710          "The pointer_type_id must refer to a defined pointer type with "
711          "storage class Function");
712   auto function = fuzzerutil::FindFunction(GetIRContext(), function_id);
713   assert(function && "The function must be defined.");
714 
715   // First we try to find a suitable existing variable.
716   // All of the local variable declarations are located in the first block.
717   for (auto& instruction : *function->begin()) {
718     if (instruction.opcode() != SpvOpVariable) {
719       continue;
720     }
721     // The existing OpVariable must have type |pointer_type_id|.
722     if (instruction.type_id() != pointer_type_id) {
723       continue;
724     }
725     // Check if the found variable is marked with PointeeValueIsIrrelevant
726     // according to |pointee_value_is_irrelevant|.
727     if (GetTransformationContext()->GetFactManager()->PointeeValueIsIrrelevant(
728             instruction.result_id()) != pointee_value_is_irrelevant) {
729       continue;
730     }
731     return instruction.result_id();
732   }
733 
734   // No such variable was found. Apply a transformation to get one.
735   uint32_t pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
736       GetIRContext(), pointer_type_id);
737   uint32_t result_id = GetFuzzerContext()->GetFreshId();
738   ApplyTransformation(TransformationAddLocalVariable(
739       result_id, pointer_type_id, function_id,
740       FindOrCreateZeroConstant(pointee_type_id, pointee_value_is_irrelevant),
741       pointee_value_is_irrelevant));
742   return result_id;
743 }
744 
FindOrCreateGlobalVariable(uint32_t pointer_type_id,bool pointee_value_is_irrelevant)745 uint32_t FuzzerPass::FindOrCreateGlobalVariable(
746     uint32_t pointer_type_id, bool pointee_value_is_irrelevant) {
747   auto pointer_type = GetIRContext()->get_type_mgr()->GetType(pointer_type_id);
748   // No unused variables in release mode.
749   (void)pointer_type;
750   assert(
751       pointer_type && pointer_type->AsPointer() &&
752       (pointer_type->AsPointer()->storage_class() == SpvStorageClassPrivate ||
753        pointer_type->AsPointer()->storage_class() ==
754            SpvStorageClassWorkgroup) &&
755       "The pointer_type_id must refer to a defined pointer type with storage "
756       "class Private or Workgroup");
757 
758   // First we try to find a suitable existing variable.
759   for (auto& instruction : GetIRContext()->module()->types_values()) {
760     if (instruction.opcode() != SpvOpVariable) {
761       continue;
762     }
763     // The existing OpVariable must have type |pointer_type_id|.
764     if (instruction.type_id() != pointer_type_id) {
765       continue;
766     }
767     // Check if the found variable is marked with PointeeValueIsIrrelevant
768     // according to |pointee_value_is_irrelevant|.
769     if (GetTransformationContext()->GetFactManager()->PointeeValueIsIrrelevant(
770             instruction.result_id()) != pointee_value_is_irrelevant) {
771       continue;
772     }
773     return instruction.result_id();
774   }
775 
776   // No such variable was found. Apply a transformation to get one.
777   uint32_t pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
778       GetIRContext(), pointer_type_id);
779   auto storage_class = fuzzerutil::GetStorageClassFromPointerType(
780       GetIRContext(), pointer_type_id);
781   uint32_t result_id = GetFuzzerContext()->GetFreshId();
782 
783   // A variable with storage class Workgroup shouldn't have an initializer.
784   if (storage_class == SpvStorageClassWorkgroup) {
785     ApplyTransformation(TransformationAddGlobalVariable(
786         result_id, pointer_type_id, SpvStorageClassWorkgroup, 0,
787         pointee_value_is_irrelevant));
788   } else {
789     ApplyTransformation(TransformationAddGlobalVariable(
790         result_id, pointer_type_id, SpvStorageClassPrivate,
791         FindOrCreateZeroConstant(pointee_type_id, pointee_value_is_irrelevant),
792         pointee_value_is_irrelevant));
793   }
794   return result_id;
795 }
796 
797 }  // namespace fuzz
798 }  // namespace spvtools
799