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