• 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/instruction_descriptor.h"
21 #include "source/fuzz/transformation_add_constant_boolean.h"
22 #include "source/fuzz/transformation_add_constant_composite.h"
23 #include "source/fuzz/transformation_add_constant_scalar.h"
24 #include "source/fuzz/transformation_add_global_undef.h"
25 #include "source/fuzz/transformation_add_type_boolean.h"
26 #include "source/fuzz/transformation_add_type_float.h"
27 #include "source/fuzz/transformation_add_type_function.h"
28 #include "source/fuzz/transformation_add_type_int.h"
29 #include "source/fuzz/transformation_add_type_matrix.h"
30 #include "source/fuzz/transformation_add_type_pointer.h"
31 #include "source/fuzz/transformation_add_type_vector.h"
32 
33 namespace spvtools {
34 namespace fuzz {
35 
FuzzerPass(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations)36 FuzzerPass::FuzzerPass(opt::IRContext* ir_context,
37                        TransformationContext* transformation_context,
38                        FuzzerContext* fuzzer_context,
39                        protobufs::TransformationSequence* transformations)
40     : ir_context_(ir_context),
41       transformation_context_(transformation_context),
42       fuzzer_context_(fuzzer_context),
43       transformations_(transformations) {}
44 
45 FuzzerPass::~FuzzerPass() = default;
46 
FindAvailableInstructions(opt::Function * function,opt::BasicBlock * block,const opt::BasicBlock::iterator & inst_it,std::function<bool (opt::IRContext *,opt::Instruction *)> instruction_is_relevant) const47 std::vector<opt::Instruction*> FuzzerPass::FindAvailableInstructions(
48     opt::Function* function, opt::BasicBlock* block,
49     const opt::BasicBlock::iterator& inst_it,
50     std::function<bool(opt::IRContext*, opt::Instruction*)>
51         instruction_is_relevant) const {
52   // TODO(afd) The following is (relatively) simple, but may end up being
53   //  prohibitively inefficient, as it walks the whole dominator tree for
54   //  every instruction that is considered.
55 
56   std::vector<opt::Instruction*> result;
57   // Consider all global declarations
58   for (auto& global : GetIRContext()->module()->types_values()) {
59     if (instruction_is_relevant(GetIRContext(), &global)) {
60       result.push_back(&global);
61     }
62   }
63 
64   // Consider all function parameters
65   function->ForEachParam(
66       [this, &instruction_is_relevant, &result](opt::Instruction* param) {
67         if (instruction_is_relevant(GetIRContext(), param)) {
68           result.push_back(param);
69         }
70       });
71 
72   // Consider all previous instructions in this block
73   for (auto prev_inst_it = block->begin(); prev_inst_it != inst_it;
74        ++prev_inst_it) {
75     if (instruction_is_relevant(GetIRContext(), &*prev_inst_it)) {
76       result.push_back(&*prev_inst_it);
77     }
78   }
79 
80   // Walk the dominator tree to consider all instructions from dominating
81   // blocks
82   auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function);
83   for (auto next_dominator = dominator_analysis->ImmediateDominator(block);
84        next_dominator != nullptr;
85        next_dominator =
86            dominator_analysis->ImmediateDominator(next_dominator)) {
87     for (auto& dominating_inst : *next_dominator) {
88       if (instruction_is_relevant(GetIRContext(), &dominating_inst)) {
89         result.push_back(&dominating_inst);
90       }
91     }
92   }
93   return result;
94 }
95 
ForEachInstructionWithInstructionDescriptor(std::function<void (opt::Function * function,opt::BasicBlock * block,opt::BasicBlock::iterator inst_it,const protobufs::InstructionDescriptor & instruction_descriptor)> action)96 void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
97     std::function<
98         void(opt::Function* function, opt::BasicBlock* block,
99              opt::BasicBlock::iterator inst_it,
100              const protobufs::InstructionDescriptor& instruction_descriptor)>
101         action) {
102   // Consider every block in every function.
103   for (auto& function : *GetIRContext()->module()) {
104     for (auto& block : function) {
105       // We now consider every instruction in the block, randomly deciding
106       // whether to apply a transformation before it.
107 
108       // In order for transformations to insert new instructions, they need to
109       // be able to identify the instruction to insert before.  We describe an
110       // instruction via its opcode, 'opc', a base instruction 'base' that has a
111       // result id, and the number of instructions with opcode 'opc' that we
112       // should skip when searching from 'base' for the desired instruction.
113       // (An instruction that has a result id is represented by its own opcode,
114       // itself as 'base', and a skip-count of 0.)
115       std::vector<std::tuple<uint32_t, SpvOp, uint32_t>>
116           base_opcode_skip_triples;
117 
118       // The initial base instruction is the block label.
119       uint32_t base = block.id();
120 
121       // Counts the number of times we have seen each opcode since we reset the
122       // base instruction.
123       std::map<SpvOp, uint32_t> skip_count;
124 
125       // Consider every instruction in the block.  The label is excluded: it is
126       // only necessary to consider it as a base in case the first instruction
127       // in the block does not have a result id.
128       for (auto inst_it = block.begin(); inst_it != block.end(); ++inst_it) {
129         if (inst_it->HasResultId()) {
130           // In the case that the instruction has a result id, we use the
131           // instruction as its own base, and clear the skip counts we have
132           // collected.
133           base = inst_it->result_id();
134           skip_count.clear();
135         }
136         const SpvOp opcode = inst_it->opcode();
137 
138         // Invoke the provided function, which might apply a transformation.
139         action(&function, &block, inst_it,
140                MakeInstructionDescriptor(
141                    base, opcode,
142                    skip_count.count(opcode) ? skip_count.at(opcode) : 0));
143 
144         if (!inst_it->HasResultId()) {
145           skip_count[opcode] =
146               skip_count.count(opcode) ? skip_count.at(opcode) + 1 : 1;
147         }
148       }
149     }
150   }
151 }
152 
FindOrCreateBoolType()153 uint32_t FuzzerPass::FindOrCreateBoolType() {
154   opt::analysis::Bool bool_type;
155   auto existing_id = GetIRContext()->get_type_mgr()->GetId(&bool_type);
156   if (existing_id) {
157     return existing_id;
158   }
159   auto result = GetFuzzerContext()->GetFreshId();
160   ApplyTransformation(TransformationAddTypeBoolean(result));
161   return result;
162 }
163 
FindOrCreate32BitIntegerType(bool is_signed)164 uint32_t FuzzerPass::FindOrCreate32BitIntegerType(bool is_signed) {
165   opt::analysis::Integer int_type(32, is_signed);
166   auto existing_id = GetIRContext()->get_type_mgr()->GetId(&int_type);
167   if (existing_id) {
168     return existing_id;
169   }
170   auto result = GetFuzzerContext()->GetFreshId();
171   ApplyTransformation(TransformationAddTypeInt(result, 32, is_signed));
172   return result;
173 }
174 
FindOrCreate32BitFloatType()175 uint32_t FuzzerPass::FindOrCreate32BitFloatType() {
176   opt::analysis::Float float_type(32);
177   auto existing_id = GetIRContext()->get_type_mgr()->GetId(&float_type);
178   if (existing_id) {
179     return existing_id;
180   }
181   auto result = GetFuzzerContext()->GetFreshId();
182   ApplyTransformation(TransformationAddTypeFloat(result, 32));
183   return result;
184 }
185 
FindOrCreateFunctionType(uint32_t return_type_id,const std::vector<uint32_t> & argument_id)186 uint32_t FuzzerPass::FindOrCreateFunctionType(
187     uint32_t return_type_id, const std::vector<uint32_t>& argument_id) {
188   // FindFunctionType has a sigle argument for OpTypeFunction operands
189   // so we will have to copy them all in this vector
190   std::vector<uint32_t> type_ids(argument_id.size() + 1);
191   type_ids[0] = return_type_id;
192   std::copy(argument_id.begin(), argument_id.end(), type_ids.begin() + 1);
193 
194   // Check if type exists
195   auto existing_id = fuzzerutil::FindFunctionType(GetIRContext(), type_ids);
196   if (existing_id) {
197     return existing_id;
198   }
199 
200   auto result = GetFuzzerContext()->GetFreshId();
201   ApplyTransformation(
202       TransformationAddTypeFunction(result, return_type_id, argument_id));
203   return result;
204 }
205 
FindOrCreateVectorType(uint32_t component_type_id,uint32_t component_count)206 uint32_t FuzzerPass::FindOrCreateVectorType(uint32_t component_type_id,
207                                             uint32_t component_count) {
208   assert(component_count >= 2 && component_count <= 4 &&
209          "Precondition: component count must be in range [2, 4].");
210   opt::analysis::Type* component_type =
211       GetIRContext()->get_type_mgr()->GetType(component_type_id);
212   assert(component_type && "Precondition: the component type must exist.");
213   opt::analysis::Vector vector_type(component_type, component_count);
214   auto existing_id = GetIRContext()->get_type_mgr()->GetId(&vector_type);
215   if (existing_id) {
216     return existing_id;
217   }
218   auto result = GetFuzzerContext()->GetFreshId();
219   ApplyTransformation(
220       TransformationAddTypeVector(result, component_type_id, component_count));
221   return result;
222 }
223 
FindOrCreateMatrixType(uint32_t column_count,uint32_t row_count)224 uint32_t FuzzerPass::FindOrCreateMatrixType(uint32_t column_count,
225                                             uint32_t row_count) {
226   assert(column_count >= 2 && column_count <= 4 &&
227          "Precondition: column count must be in range [2, 4].");
228   assert(row_count >= 2 && row_count <= 4 &&
229          "Precondition: row count must be in range [2, 4].");
230   uint32_t column_type_id =
231       FindOrCreateVectorType(FindOrCreate32BitFloatType(), row_count);
232   opt::analysis::Type* column_type =
233       GetIRContext()->get_type_mgr()->GetType(column_type_id);
234   opt::analysis::Matrix matrix_type(column_type, column_count);
235   auto existing_id = GetIRContext()->get_type_mgr()->GetId(&matrix_type);
236   if (existing_id) {
237     return existing_id;
238   }
239   auto result = GetFuzzerContext()->GetFreshId();
240   ApplyTransformation(
241       TransformationAddTypeMatrix(result, column_type_id, column_count));
242   return result;
243 }
244 
FindOrCreatePointerType(uint32_t base_type_id,SpvStorageClass storage_class)245 uint32_t FuzzerPass::FindOrCreatePointerType(uint32_t base_type_id,
246                                              SpvStorageClass storage_class) {
247   // We do not use the type manager here, due to problems related to isomorphic
248   // but distinct structs not being regarded as different.
249   auto existing_id = fuzzerutil::MaybeGetPointerType(
250       GetIRContext(), base_type_id, storage_class);
251   if (existing_id) {
252     return existing_id;
253   }
254   auto result = GetFuzzerContext()->GetFreshId();
255   ApplyTransformation(
256       TransformationAddTypePointer(result, storage_class, base_type_id));
257   return result;
258 }
259 
FindOrCreatePointerTo32BitIntegerType(bool is_signed,SpvStorageClass storage_class)260 uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType(
261     bool is_signed, SpvStorageClass storage_class) {
262   return FindOrCreatePointerType(FindOrCreate32BitIntegerType(is_signed),
263                                  storage_class);
264 }
265 
FindOrCreate32BitIntegerConstant(uint32_t word,bool is_signed)266 uint32_t FuzzerPass::FindOrCreate32BitIntegerConstant(uint32_t word,
267                                                       bool is_signed) {
268   auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed);
269   opt::analysis::IntConstant int_constant(
270       GetIRContext()->get_type_mgr()->GetType(uint32_type_id)->AsInteger(),
271       {word});
272   auto existing_constant =
273       GetIRContext()->get_constant_mgr()->FindConstant(&int_constant);
274   if (existing_constant) {
275     return GetIRContext()
276         ->get_constant_mgr()
277         ->GetDefiningInstruction(existing_constant)
278         ->result_id();
279   }
280   auto result = GetFuzzerContext()->GetFreshId();
281   ApplyTransformation(
282       TransformationAddConstantScalar(result, uint32_type_id, {word}));
283   return result;
284 }
285 
FindOrCreate32BitFloatConstant(uint32_t word)286 uint32_t FuzzerPass::FindOrCreate32BitFloatConstant(uint32_t word) {
287   auto float_type_id = FindOrCreate32BitFloatType();
288   opt::analysis::FloatConstant float_constant(
289       GetIRContext()->get_type_mgr()->GetType(float_type_id)->AsFloat(),
290       {word});
291   auto existing_constant =
292       GetIRContext()->get_constant_mgr()->FindConstant(&float_constant);
293   if (existing_constant) {
294     return GetIRContext()
295         ->get_constant_mgr()
296         ->GetDefiningInstruction(existing_constant)
297         ->result_id();
298   }
299   auto result = GetFuzzerContext()->GetFreshId();
300   ApplyTransformation(
301       TransformationAddConstantScalar(result, float_type_id, {word}));
302   return result;
303 }
304 
FindOrCreateBoolConstant(bool value)305 uint32_t FuzzerPass::FindOrCreateBoolConstant(bool value) {
306   auto bool_type_id = FindOrCreateBoolType();
307   opt::analysis::BoolConstant bool_constant(
308       GetIRContext()->get_type_mgr()->GetType(bool_type_id)->AsBool(), value);
309   auto existing_constant =
310       GetIRContext()->get_constant_mgr()->FindConstant(&bool_constant);
311   if (existing_constant) {
312     return GetIRContext()
313         ->get_constant_mgr()
314         ->GetDefiningInstruction(existing_constant)
315         ->result_id();
316   }
317   auto result = GetFuzzerContext()->GetFreshId();
318   ApplyTransformation(TransformationAddConstantBoolean(result, value));
319   return result;
320 }
321 
FindOrCreateGlobalUndef(uint32_t type_id)322 uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) {
323   for (auto& inst : GetIRContext()->types_values()) {
324     if (inst.opcode() == SpvOpUndef && inst.type_id() == type_id) {
325       return inst.result_id();
326     }
327   }
328   auto result = GetFuzzerContext()->GetFreshId();
329   ApplyTransformation(TransformationAddGlobalUndef(result, type_id));
330   return result;
331 }
332 
333 std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
GetAvailableBasicTypesAndPointers(SpvStorageClass storage_class) const334 FuzzerPass::GetAvailableBasicTypesAndPointers(
335     SpvStorageClass storage_class) const {
336   // Records all of the basic types available in the module.
337   std::set<uint32_t> basic_types;
338 
339   // For each basic type, records all the associated pointer types that target
340   // the basic type and that have |storage_class| as their storage class.
341   std::map<uint32_t, std::vector<uint32_t>> basic_type_to_pointers;
342 
343   for (auto& inst : GetIRContext()->types_values()) {
344     // For each basic type that we come across, record type, and the fact that
345     // we cannot yet have seen any pointers that use the basic type as its
346     // pointee type.
347     //
348     // For pointer types with basic pointee types, associate the pointer type
349     // with the basic type.
350     switch (inst.opcode()) {
351       case SpvOpTypeBool:
352       case SpvOpTypeFloat:
353       case SpvOpTypeInt:
354       case SpvOpTypeMatrix:
355       case SpvOpTypeVector:
356         // These are all basic types.
357         basic_types.insert(inst.result_id());
358         basic_type_to_pointers.insert({inst.result_id(), {}});
359         break;
360       case SpvOpTypeArray:
361         // An array type is basic if its base type is basic.
362         if (basic_types.count(inst.GetSingleWordInOperand(0))) {
363           basic_types.insert(inst.result_id());
364           basic_type_to_pointers.insert({inst.result_id(), {}});
365         }
366         break;
367       case SpvOpTypeStruct: {
368         // A struct type is basic if all of its members are basic.
369         bool all_members_are_basic_types = true;
370         for (uint32_t i = 0; i < inst.NumInOperands(); i++) {
371           if (!basic_types.count(inst.GetSingleWordInOperand(i))) {
372             all_members_are_basic_types = false;
373             break;
374           }
375         }
376         if (all_members_are_basic_types) {
377           basic_types.insert(inst.result_id());
378           basic_type_to_pointers.insert({inst.result_id(), {}});
379         }
380         break;
381       }
382       case SpvOpTypePointer: {
383         // We are interested in the pointer if its pointee type is basic and it
384         // has the right storage class.
385         auto pointee_type = inst.GetSingleWordInOperand(1);
386         if (inst.GetSingleWordInOperand(0) == storage_class &&
387             basic_types.count(pointee_type)) {
388           // The pointer has the desired storage class, and its pointee type is
389           // a basic type, so we are interested in it.  Associate it with its
390           // basic type.
391           basic_type_to_pointers.at(pointee_type).push_back(inst.result_id());
392         }
393         break;
394       }
395       default:
396         break;
397     }
398   }
399   return {{basic_types.begin(), basic_types.end()}, basic_type_to_pointers};
400 }
401 
FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id)402 uint32_t FuzzerPass::FindOrCreateZeroConstant(
403     uint32_t scalar_or_composite_type_id) {
404   auto type_instruction =
405       GetIRContext()->get_def_use_mgr()->GetDef(scalar_or_composite_type_id);
406   assert(type_instruction && "The type instruction must exist.");
407   switch (type_instruction->opcode()) {
408     case SpvOpTypeBool:
409       return FindOrCreateBoolConstant(false);
410     case SpvOpTypeFloat:
411       return FindOrCreate32BitFloatConstant(0);
412     case SpvOpTypeInt:
413       return FindOrCreate32BitIntegerConstant(
414           0, type_instruction->GetSingleWordInOperand(1) != 0);
415     case SpvOpTypeArray: {
416       return GetZeroConstantForHomogeneousComposite(
417           *type_instruction, type_instruction->GetSingleWordInOperand(0),
418           fuzzerutil::GetArraySize(*type_instruction, GetIRContext()));
419     }
420     case SpvOpTypeMatrix:
421     case SpvOpTypeVector: {
422       return GetZeroConstantForHomogeneousComposite(
423           *type_instruction, type_instruction->GetSingleWordInOperand(0),
424           type_instruction->GetSingleWordInOperand(1));
425     }
426     case SpvOpTypeStruct: {
427       std::vector<const opt::analysis::Constant*> field_zero_constants;
428       std::vector<uint32_t> field_zero_ids;
429       for (uint32_t index = 0; index < type_instruction->NumInOperands();
430            index++) {
431         uint32_t field_constant_id = FindOrCreateZeroConstant(
432             type_instruction->GetSingleWordInOperand(index));
433         field_zero_ids.push_back(field_constant_id);
434         field_zero_constants.push_back(
435             GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
436                 field_constant_id));
437       }
438       return FindOrCreateCompositeConstant(
439           *type_instruction, field_zero_constants, field_zero_ids);
440     }
441     default:
442       assert(false && "Unknown type.");
443       return 0;
444   }
445 }
446 
FindOrCreateCompositeConstant(const opt::Instruction & composite_type_instruction,const std::vector<const opt::analysis::Constant * > & constants,const std::vector<uint32_t> & constant_ids)447 uint32_t FuzzerPass::FindOrCreateCompositeConstant(
448     const opt::Instruction& composite_type_instruction,
449     const std::vector<const opt::analysis::Constant*>& constants,
450     const std::vector<uint32_t>& constant_ids) {
451   assert(constants.size() == constant_ids.size() &&
452          "Precondition: |constants| and |constant_ids| must be in "
453          "correspondence.");
454 
455   opt::analysis::Type* composite_type = GetIRContext()->get_type_mgr()->GetType(
456       composite_type_instruction.result_id());
457   std::unique_ptr<opt::analysis::Constant> composite_constant;
458   if (composite_type->AsArray()) {
459     composite_constant = MakeUnique<opt::analysis::ArrayConstant>(
460         composite_type->AsArray(), constants);
461   } else if (composite_type->AsMatrix()) {
462     composite_constant = MakeUnique<opt::analysis::MatrixConstant>(
463         composite_type->AsMatrix(), constants);
464   } else if (composite_type->AsStruct()) {
465     composite_constant = MakeUnique<opt::analysis::StructConstant>(
466         composite_type->AsStruct(), constants);
467   } else if (composite_type->AsVector()) {
468     composite_constant = MakeUnique<opt::analysis::VectorConstant>(
469         composite_type->AsVector(), constants);
470   } else {
471     assert(false &&
472            "Precondition: |composite_type| must declare a composite type.");
473     return 0;
474   }
475 
476   uint32_t existing_constant =
477       GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
478           composite_constant.get(), composite_type_instruction.result_id());
479   if (existing_constant) {
480     return existing_constant;
481   }
482   uint32_t result = GetFuzzerContext()->GetFreshId();
483   ApplyTransformation(TransformationAddConstantComposite(
484       result, composite_type_instruction.result_id(), constant_ids));
485   return result;
486 }
487 
GetZeroConstantForHomogeneousComposite(const opt::Instruction & composite_type_instruction,uint32_t component_type_id,uint32_t num_components)488 uint32_t FuzzerPass::GetZeroConstantForHomogeneousComposite(
489     const opt::Instruction& composite_type_instruction,
490     uint32_t component_type_id, uint32_t num_components) {
491   std::vector<const opt::analysis::Constant*> zero_constants;
492   std::vector<uint32_t> zero_ids;
493   uint32_t zero_component = FindOrCreateZeroConstant(component_type_id);
494   const opt::analysis::Constant* registered_zero_component =
495       GetIRContext()->get_constant_mgr()->FindDeclaredConstant(zero_component);
496   for (uint32_t i = 0; i < num_components; i++) {
497     zero_constants.push_back(registered_zero_component);
498     zero_ids.push_back(zero_component);
499   }
500   return FindOrCreateCompositeConstant(composite_type_instruction,
501                                        zero_constants, zero_ids);
502 }
503 
504 }  // namespace fuzz
505 }  // namespace spvtools
506