• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "source/fuzz/fuzzer_pass_add_equation_instructions.h"
16 
17 #include <vector>
18 
19 #include "source/fuzz/fuzzer_util.h"
20 #include "source/fuzz/transformation_equation_instruction.h"
21 
22 namespace spvtools {
23 namespace fuzz {
24 namespace {
25 
IsBitWidthSupported(opt::IRContext * ir_context,uint32_t bit_width)26 bool IsBitWidthSupported(opt::IRContext* ir_context, uint32_t bit_width) {
27   switch (bit_width) {
28     case 32:
29       return true;
30     case 64:
31       return ir_context->get_feature_mgr()->HasCapability(
32                  SpvCapabilityFloat64) &&
33              ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt64);
34     case 16:
35       return ir_context->get_feature_mgr()->HasCapability(
36                  SpvCapabilityFloat16) &&
37              ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt16);
38     default:
39       return false;
40   }
41 }
42 
43 }  // namespace
44 
FuzzerPassAddEquationInstructions(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations)45 FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions(
46     opt::IRContext* ir_context, TransformationContext* transformation_context,
47     FuzzerContext* fuzzer_context,
48     protobufs::TransformationSequence* transformations)
49     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
50                  transformations) {}
51 
Apply()52 void FuzzerPassAddEquationInstructions::Apply() {
53   ForEachInstructionWithInstructionDescriptor(
54       [this](opt::Function* function, opt::BasicBlock* block,
55              opt::BasicBlock::iterator inst_it,
56              const protobufs::InstructionDescriptor& instruction_descriptor) {
57         if (!GetFuzzerContext()->ChoosePercentage(
58                 GetFuzzerContext()->GetChanceOfAddingEquationInstruction())) {
59           return;
60         }
61 
62         // Check that it is OK to add an equation instruction before the given
63         // instruction in principle - e.g. check that this does not lead to
64         // inserting before an OpVariable or OpPhi instruction.  We use OpIAdd
65         // as an example opcode for this check, to be representative of *some*
66         // opcode that defines an equation, even though we may choose a
67         // different opcode below.
68         if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpIAdd, inst_it)) {
69           return;
70         }
71 
72         // Get all available instructions with result ids and types that are not
73         // OpUndef.
74         std::vector<opt::Instruction*> available_instructions =
75             FindAvailableInstructions(
76                 function, block, inst_it,
77                 [this](opt::IRContext* /*unused*/,
78                        opt::Instruction* instruction) -> bool {
79                   return instruction->result_id() && instruction->type_id() &&
80                          instruction->opcode() != SpvOpUndef &&
81                          !GetTransformationContext()
82                               ->GetFactManager()
83                               ->IdIsIrrelevant(instruction->result_id());
84                 });
85 
86         // Try the opcodes for which we know how to make ids at random until
87         // something works.
88         std::vector<SpvOp> candidate_opcodes = {
89             SpvOpIAdd,        SpvOpISub,        SpvOpLogicalNot, SpvOpSNegate,
90             SpvOpConvertUToF, SpvOpConvertSToF, SpvOpBitcast};
91         do {
92           auto opcode =
93               GetFuzzerContext()->RemoveAtRandomIndex(&candidate_opcodes);
94           switch (opcode) {
95             case SpvOpConvertSToF:
96             case SpvOpConvertUToF: {
97               std::vector<const opt::Instruction*> candidate_instructions;
98               for (const auto* inst :
99                    GetIntegerInstructions(available_instructions)) {
100                 const auto* type =
101                     GetIRContext()->get_type_mgr()->GetType(inst->type_id());
102                 assert(type && "|inst| has invalid type");
103 
104                 if (const auto* vector_type = type->AsVector()) {
105                   type = vector_type->element_type();
106                 }
107 
108                 if (IsBitWidthSupported(GetIRContext(),
109                                         type->AsInteger()->width())) {
110                   candidate_instructions.push_back(inst);
111                 }
112               }
113 
114               if (candidate_instructions.empty()) {
115                 break;
116               }
117 
118               const auto* operand =
119                   candidate_instructions[GetFuzzerContext()->RandomIndex(
120                       candidate_instructions)];
121 
122               const auto* type =
123                   GetIRContext()->get_type_mgr()->GetType(operand->type_id());
124               assert(type && "Operand has invalid type");
125 
126               // Make sure a result type exists in the module.
127               if (const auto* vector = type->AsVector()) {
128                 // We store element count in a separate variable since the
129                 // call FindOrCreate* functions below might invalidate
130                 // |vector| pointer.
131                 const auto element_count = vector->element_count();
132 
133                 FindOrCreateVectorType(
134                     FindOrCreateFloatType(
135                         vector->element_type()->AsInteger()->width()),
136                     element_count);
137               } else {
138                 FindOrCreateFloatType(type->AsInteger()->width());
139               }
140 
141               ApplyTransformation(TransformationEquationInstruction(
142                   GetFuzzerContext()->GetFreshId(), opcode,
143                   {operand->result_id()}, instruction_descriptor));
144               return;
145             }
146             case SpvOpBitcast: {
147               const auto candidate_instructions =
148                   GetNumericalInstructions(available_instructions);
149 
150               if (!candidate_instructions.empty()) {
151                 const auto* operand_inst =
152                     candidate_instructions[GetFuzzerContext()->RandomIndex(
153                         candidate_instructions)];
154                 const auto* operand_type =
155                     GetIRContext()->get_type_mgr()->GetType(
156                         operand_inst->type_id());
157                 assert(operand_type && "Operand instruction has invalid type");
158 
159                 // Make sure a result type exists in the module.
160                 //
161                 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3539):
162                 //  The only constraint on the types of OpBitcast's parameters
163                 //  is that they must have the same number of bits. Consider
164                 //  improving the code below to support this in full.
165                 if (const auto* vector = operand_type->AsVector()) {
166                   // We store element count in a separate variable since the
167                   // call FindOrCreate* functions below might invalidate
168                   // |vector| pointer.
169                   const auto element_count = vector->element_count();
170 
171                   uint32_t element_type_id;
172                   if (const auto* int_type =
173                           vector->element_type()->AsInteger()) {
174                     element_type_id = FindOrCreateFloatType(int_type->width());
175                   } else {
176                     assert(vector->element_type()->AsFloat() &&
177                            "Vector must have numerical elements");
178                     element_type_id = FindOrCreateIntegerType(
179                         vector->element_type()->AsFloat()->width(),
180                         GetFuzzerContext()->ChooseEven());
181                   }
182 
183                   FindOrCreateVectorType(element_type_id, element_count);
184                 } else if (const auto* int_type = operand_type->AsInteger()) {
185                   FindOrCreateFloatType(int_type->width());
186                 } else {
187                   assert(operand_type->AsFloat() &&
188                          "Operand is not a scalar of numerical type");
189                   FindOrCreateIntegerType(operand_type->AsFloat()->width(),
190                                           GetFuzzerContext()->ChooseEven());
191                 }
192 
193                 ApplyTransformation(TransformationEquationInstruction(
194                     GetFuzzerContext()->GetFreshId(), opcode,
195                     {operand_inst->result_id()}, instruction_descriptor));
196                 return;
197               }
198             } break;
199             case SpvOpIAdd:
200             case SpvOpISub: {
201               // Instructions of integer (scalar or vector) result type are
202               // suitable for these opcodes.
203               auto integer_instructions =
204                   GetIntegerInstructions(available_instructions);
205               if (!integer_instructions.empty()) {
206                 // There is at least one such instruction, so pick one at random
207                 // for the LHS of an equation.
208                 auto lhs = integer_instructions.at(
209                     GetFuzzerContext()->RandomIndex(integer_instructions));
210 
211                 // For the RHS, we can use any instruction with an integer
212                 // scalar/vector result type of the same number of components
213                 // and the same bit-width for the underlying integer type.
214 
215                 // Work out the element count and bit-width.
216                 auto lhs_type =
217                     GetIRContext()->get_type_mgr()->GetType(lhs->type_id());
218                 uint32_t lhs_element_count;
219                 uint32_t lhs_bit_width;
220                 if (lhs_type->AsVector()) {
221                   lhs_element_count = lhs_type->AsVector()->element_count();
222                   lhs_bit_width = lhs_type->AsVector()
223                                       ->element_type()
224                                       ->AsInteger()
225                                       ->width();
226                 } else {
227                   lhs_element_count = 1;
228                   lhs_bit_width = lhs_type->AsInteger()->width();
229                 }
230 
231                 // Get all the instructions that match on element count and
232                 // bit-width.
233                 auto candidate_rhs_instructions = RestrictToElementBitWidth(
234                     RestrictToVectorWidth(integer_instructions,
235                                           lhs_element_count),
236                     lhs_bit_width);
237 
238                 // Choose a RHS instruction at random; there is guaranteed to
239                 // be at least one choice as the LHS will be available.
240                 auto rhs = candidate_rhs_instructions.at(
241                     GetFuzzerContext()->RandomIndex(
242                         candidate_rhs_instructions));
243 
244                 // Add the equation instruction.
245                 ApplyTransformation(TransformationEquationInstruction(
246                     GetFuzzerContext()->GetFreshId(), opcode,
247                     {lhs->result_id(), rhs->result_id()},
248                     instruction_descriptor));
249                 return;
250               }
251               break;
252             }
253             case SpvOpLogicalNot: {
254               // Choose any available instruction of boolean scalar/vector
255               // result type and equate its negation with a fresh id.
256               auto boolean_instructions =
257                   GetBooleanInstructions(available_instructions);
258               if (!boolean_instructions.empty()) {
259                 ApplyTransformation(TransformationEquationInstruction(
260                     GetFuzzerContext()->GetFreshId(), opcode,
261                     {boolean_instructions
262                          .at(GetFuzzerContext()->RandomIndex(
263                              boolean_instructions))
264                          ->result_id()},
265                     instruction_descriptor));
266                 return;
267               }
268               break;
269             }
270             case SpvOpSNegate: {
271               // Similar to OpLogicalNot, but for signed integer negation.
272               auto integer_instructions =
273                   GetIntegerInstructions(available_instructions);
274               if (!integer_instructions.empty()) {
275                 ApplyTransformation(TransformationEquationInstruction(
276                     GetFuzzerContext()->GetFreshId(), opcode,
277                     {integer_instructions
278                          .at(GetFuzzerContext()->RandomIndex(
279                              integer_instructions))
280                          ->result_id()},
281                     instruction_descriptor));
282                 return;
283               }
284               break;
285             }
286             default:
287               assert(false && "Unexpected opcode.");
288               break;
289           }
290         } while (!candidate_opcodes.empty());
291         // Reaching here means that we did not manage to apply any
292         // transformation at this point of the module.
293       });
294 }
295 
296 std::vector<opt::Instruction*>
GetIntegerInstructions(const std::vector<opt::Instruction * > & instructions) const297 FuzzerPassAddEquationInstructions::GetIntegerInstructions(
298     const std::vector<opt::Instruction*>& instructions) const {
299   std::vector<opt::Instruction*> result;
300   for (auto& inst : instructions) {
301     auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
302     if (type->AsInteger() ||
303         (type->AsVector() && type->AsVector()->element_type()->AsInteger())) {
304       result.push_back(inst);
305     }
306   }
307   return result;
308 }
309 
310 std::vector<opt::Instruction*>
GetFloatInstructions(const std::vector<opt::Instruction * > & instructions) const311 FuzzerPassAddEquationInstructions::GetFloatInstructions(
312     const std::vector<opt::Instruction*>& instructions) const {
313   std::vector<opt::Instruction*> result;
314   for (auto& inst : instructions) {
315     auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
316     if (type->AsFloat() ||
317         (type->AsVector() && type->AsVector()->element_type()->AsFloat())) {
318       result.push_back(inst);
319     }
320   }
321   return result;
322 }
323 
324 std::vector<opt::Instruction*>
GetBooleanInstructions(const std::vector<opt::Instruction * > & instructions) const325 FuzzerPassAddEquationInstructions::GetBooleanInstructions(
326     const std::vector<opt::Instruction*>& instructions) const {
327   std::vector<opt::Instruction*> result;
328   for (auto& inst : instructions) {
329     auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
330     if (type->AsBool() ||
331         (type->AsVector() && type->AsVector()->element_type()->AsBool())) {
332       result.push_back(inst);
333     }
334   }
335   return result;
336 }
337 
338 std::vector<opt::Instruction*>
RestrictToVectorWidth(const std::vector<opt::Instruction * > & instructions,uint32_t vector_width) const339 FuzzerPassAddEquationInstructions::RestrictToVectorWidth(
340     const std::vector<opt::Instruction*>& instructions,
341     uint32_t vector_width) const {
342   std::vector<opt::Instruction*> result;
343   for (auto& inst : instructions) {
344     auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
345     // Get the vector width of |inst|, which is 1 if |inst| is a scalar and is
346     // otherwise derived from its vector type.
347     uint32_t other_vector_width =
348         type->AsVector() ? type->AsVector()->element_count() : 1;
349     // Keep |inst| if the vector widths match.
350     if (vector_width == other_vector_width) {
351       result.push_back(inst);
352     }
353   }
354   return result;
355 }
356 
357 std::vector<opt::Instruction*>
RestrictToElementBitWidth(const std::vector<opt::Instruction * > & instructions,uint32_t bit_width) const358 FuzzerPassAddEquationInstructions::RestrictToElementBitWidth(
359     const std::vector<opt::Instruction*>& instructions,
360     uint32_t bit_width) const {
361   std::vector<opt::Instruction*> result;
362   for (auto& inst : instructions) {
363     const opt::analysis::Type* type =
364         GetIRContext()->get_type_mgr()->GetType(inst->type_id());
365     if (type->AsVector()) {
366       type = type->AsVector()->element_type();
367     }
368     assert((type->AsInteger() || type->AsFloat()) &&
369            "Precondition: all input instructions must "
370            "have integer or float scalar or vector type.");
371     if ((type->AsInteger() && type->AsInteger()->width() == bit_width) ||
372         (type->AsFloat() && type->AsFloat()->width() == bit_width)) {
373       result.push_back(inst);
374     }
375   }
376   return result;
377 }
378 
379 std::vector<opt::Instruction*>
GetNumericalInstructions(const std::vector<opt::Instruction * > & instructions) const380 FuzzerPassAddEquationInstructions::GetNumericalInstructions(
381     const std::vector<opt::Instruction*>& instructions) const {
382   std::vector<opt::Instruction*> result;
383 
384   for (auto* inst : instructions) {
385     const auto* type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
386     assert(type && "Instruction has invalid type");
387 
388     if (const auto* vector_type = type->AsVector()) {
389       type = vector_type->element_type();
390     }
391 
392     if (!type->AsInteger() && !type->AsFloat()) {
393       // Only numerical scalars or vectors of numerical components are
394       // supported.
395       continue;
396     }
397 
398     if (!IsBitWidthSupported(GetIRContext(), type->AsInteger()
399                                                  ? type->AsInteger()->width()
400                                                  : type->AsFloat()->width())) {
401       continue;
402     }
403 
404     result.push_back(inst);
405   }
406 
407   return result;
408 }
409 
410 }  // namespace fuzz
411 }  // namespace spvtools
412