• 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_construct_composites.h"
16 
17 #include <cmath>
18 #include <memory>
19 
20 #include "source/fuzz/fuzzer_util.h"
21 #include "source/fuzz/transformation_composite_construct.h"
22 #include "source/util/make_unique.h"
23 
24 namespace spvtools {
25 namespace fuzz {
26 
FuzzerPassConstructComposites(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations)27 FuzzerPassConstructComposites::FuzzerPassConstructComposites(
28     opt::IRContext* ir_context, TransformationContext* transformation_context,
29     FuzzerContext* fuzzer_context,
30     protobufs::TransformationSequence* transformations)
31     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
32                  transformations) {}
33 
34 FuzzerPassConstructComposites::~FuzzerPassConstructComposites() = default;
35 
Apply()36 void FuzzerPassConstructComposites::Apply() {
37   // Gather up the ids of all composite types.
38   std::vector<uint32_t> composite_type_ids;
39   for (auto& inst : GetIRContext()->types_values()) {
40     if (fuzzerutil::IsCompositeType(
41             GetIRContext()->get_type_mgr()->GetType(inst.result_id()))) {
42       composite_type_ids.push_back(inst.result_id());
43     }
44   }
45 
46   ForEachInstructionWithInstructionDescriptor(
47       [this, &composite_type_ids](
48           opt::Function* function, opt::BasicBlock* block,
49           opt::BasicBlock::iterator inst_it,
50           const protobufs::InstructionDescriptor& instruction_descriptor)
51           -> void {
52         // Check whether it is legitimate to insert a composite construction
53         // before the instruction.
54         if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
55                 SpvOpCompositeConstruct, inst_it)) {
56           return;
57         }
58 
59         // Randomly decide whether to try inserting an object copy here.
60         if (!GetFuzzerContext()->ChoosePercentage(
61                 GetFuzzerContext()->GetChanceOfConstructingComposite())) {
62           return;
63         }
64 
65         // For each instruction that is available at this program point (i.e. an
66         // instruction that is global or whose definition strictly dominates the
67         // program point) and suitable for making a synonym of, associate it
68         // with the id of its result type.
69         TypeIdToInstructions type_id_to_available_instructions;
70         for (auto instruction : FindAvailableInstructions(
71                  function, block, inst_it, fuzzerutil::CanMakeSynonymOf)) {
72           RecordAvailableInstruction(instruction,
73                                      &type_id_to_available_instructions);
74         }
75 
76         // At this point, |composite_type_ids| captures all the composite types
77         // we could try to create, while |type_id_to_available_instructions|
78         // captures all the available result ids we might use, organized by
79         // type.
80 
81         // Now we try to find a composite that we can construct.  We might not
82         // manage, if there is a paucity of available ingredients in the module
83         // (e.g. if our only available composite was a boolean vector and we had
84         // no instructions generating boolean result types available).
85         //
86         // If we succeed, |chosen_composite_type| will end up being non-zero,
87         // and |constructor_arguments| will end up giving us result ids suitable
88         // for constructing a composite of that type.  Otherwise these variables
89         // will remain 0 and null respectively.
90         uint32_t chosen_composite_type = 0;
91         std::vector<uint32_t> constructor_arguments;
92 
93         // Initially, all composite type ids are available for us to try.  Keep
94         // trying until we run out of options.
95         auto composites_to_try_constructing = composite_type_ids;
96         while (!composites_to_try_constructing.empty()) {
97           // Remove a composite type from the composite types left for us to
98           // try.
99           auto next_composite_to_try_constructing =
100               GetFuzzerContext()->RemoveAtRandomIndex(
101                   &composites_to_try_constructing);
102 
103           // Now try to construct a composite of this type, using an appropriate
104           // helper method depending on the kind of composite type.
105           auto composite_type_inst = GetIRContext()->get_def_use_mgr()->GetDef(
106               next_composite_to_try_constructing);
107           switch (composite_type_inst->opcode()) {
108             case SpvOpTypeArray:
109               constructor_arguments = FindComponentsToConstructArray(
110                   *composite_type_inst, type_id_to_available_instructions);
111               break;
112             case SpvOpTypeMatrix:
113               constructor_arguments = FindComponentsToConstructMatrix(
114                   *composite_type_inst, type_id_to_available_instructions);
115               break;
116             case SpvOpTypeStruct:
117               constructor_arguments = FindComponentsToConstructStruct(
118                   *composite_type_inst, type_id_to_available_instructions);
119               break;
120             case SpvOpTypeVector:
121               constructor_arguments = FindComponentsToConstructVector(
122                   *composite_type_inst, type_id_to_available_instructions);
123               break;
124             default:
125               assert(false &&
126                      "The space of possible composite types should be covered "
127                      "by the above cases.");
128               break;
129           }
130           if (!constructor_arguments.empty()) {
131             // We succeeded!  Note the composite type we finally settled on, and
132             // exit from the loop.
133             chosen_composite_type = next_composite_to_try_constructing;
134             break;
135           }
136         }
137 
138         if (!chosen_composite_type) {
139           // We did not manage to make a composite; return 0 to indicate that no
140           // instructions were added.
141           assert(constructor_arguments.empty());
142           return;
143         }
144         assert(!constructor_arguments.empty());
145 
146         // Make and apply a transformation.
147         ApplyTransformation(TransformationCompositeConstruct(
148             chosen_composite_type, constructor_arguments,
149             instruction_descriptor, GetFuzzerContext()->GetFreshId()));
150       });
151 }
152 
RecordAvailableInstruction(opt::Instruction * inst,TypeIdToInstructions * type_id_to_available_instructions)153 void FuzzerPassConstructComposites::RecordAvailableInstruction(
154     opt::Instruction* inst,
155     TypeIdToInstructions* type_id_to_available_instructions) {
156   if (type_id_to_available_instructions->count(inst->type_id()) == 0) {
157     (*type_id_to_available_instructions)[inst->type_id()] = {};
158   }
159   type_id_to_available_instructions->at(inst->type_id()).push_back(inst);
160 }
161 
162 std::vector<uint32_t>
FindComponentsToConstructArray(const opt::Instruction & array_type_instruction,const TypeIdToInstructions & type_id_to_available_instructions)163 FuzzerPassConstructComposites::FindComponentsToConstructArray(
164     const opt::Instruction& array_type_instruction,
165     const TypeIdToInstructions& type_id_to_available_instructions) {
166   assert(array_type_instruction.opcode() == SpvOpTypeArray &&
167          "Precondition: instruction must be an array type.");
168 
169   // Get the element type for the array.
170   auto element_type_id = array_type_instruction.GetSingleWordInOperand(0);
171 
172   // Get all instructions at our disposal that compute something of this element
173   // type.
174   auto available_instructions =
175       type_id_to_available_instructions.find(element_type_id);
176 
177   if (available_instructions == type_id_to_available_instructions.cend()) {
178     // If there are not any instructions available that compute the element type
179     // of the array then we are not in a position to construct a composite with
180     // this array type.
181     return {};
182   }
183 
184   uint32_t array_length =
185       GetIRContext()
186           ->get_def_use_mgr()
187           ->GetDef(array_type_instruction.GetSingleWordInOperand(1))
188           ->GetSingleWordInOperand(0);
189 
190   std::vector<uint32_t> result;
191   for (uint32_t index = 0; index < array_length; index++) {
192     result.push_back(available_instructions
193                          ->second[GetFuzzerContext()->RandomIndex(
194                              available_instructions->second)]
195                          ->result_id());
196   }
197   return result;
198 }
199 
200 std::vector<uint32_t>
FindComponentsToConstructMatrix(const opt::Instruction & matrix_type_instruction,const TypeIdToInstructions & type_id_to_available_instructions)201 FuzzerPassConstructComposites::FindComponentsToConstructMatrix(
202     const opt::Instruction& matrix_type_instruction,
203     const TypeIdToInstructions& type_id_to_available_instructions) {
204   assert(matrix_type_instruction.opcode() == SpvOpTypeMatrix &&
205          "Precondition: instruction must be a matrix type.");
206 
207   // Get the element type for the matrix.
208   auto element_type_id = matrix_type_instruction.GetSingleWordInOperand(0);
209 
210   // Get all instructions at our disposal that compute something of this element
211   // type.
212   auto available_instructions =
213       type_id_to_available_instructions.find(element_type_id);
214 
215   if (available_instructions == type_id_to_available_instructions.cend()) {
216     // If there are not any instructions available that compute the element type
217     // of the matrix then we are not in a position to construct a composite with
218     // this matrix type.
219     return {};
220   }
221   std::vector<uint32_t> result;
222   for (uint32_t index = 0;
223        index < matrix_type_instruction.GetSingleWordInOperand(1); index++) {
224     result.push_back(available_instructions
225                          ->second[GetFuzzerContext()->RandomIndex(
226                              available_instructions->second)]
227                          ->result_id());
228   }
229   return result;
230 }
231 
232 std::vector<uint32_t>
FindComponentsToConstructStruct(const opt::Instruction & struct_type_instruction,const TypeIdToInstructions & type_id_to_available_instructions)233 FuzzerPassConstructComposites::FindComponentsToConstructStruct(
234     const opt::Instruction& struct_type_instruction,
235     const TypeIdToInstructions& type_id_to_available_instructions) {
236   assert(struct_type_instruction.opcode() == SpvOpTypeStruct &&
237          "Precondition: instruction must be a struct type.");
238   std::vector<uint32_t> result;
239   // Consider the type of each field of the struct.
240   for (uint32_t in_operand_index = 0;
241        in_operand_index < struct_type_instruction.NumInOperands();
242        in_operand_index++) {
243     auto element_type_id =
244         struct_type_instruction.GetSingleWordInOperand(in_operand_index);
245     // Find the instructions at our disposal that compute something of the field
246     // type.
247     auto available_instructions =
248         type_id_to_available_instructions.find(element_type_id);
249     if (available_instructions == type_id_to_available_instructions.cend()) {
250       // If there are no such instructions, we cannot construct a composite of
251       // this struct type.
252       return {};
253     }
254     result.push_back(available_instructions
255                          ->second[GetFuzzerContext()->RandomIndex(
256                              available_instructions->second)]
257                          ->result_id());
258   }
259   return result;
260 }
261 
262 std::vector<uint32_t>
FindComponentsToConstructVector(const opt::Instruction & vector_type_instruction,const TypeIdToInstructions & type_id_to_available_instructions)263 FuzzerPassConstructComposites::FindComponentsToConstructVector(
264     const opt::Instruction& vector_type_instruction,
265     const TypeIdToInstructions& type_id_to_available_instructions) {
266   assert(vector_type_instruction.opcode() == SpvOpTypeVector &&
267          "Precondition: instruction must be a vector type.");
268 
269   // Get details of the type underlying the vector, and the width of the vector,
270   // for convenience.
271   auto element_type_id = vector_type_instruction.GetSingleWordInOperand(0);
272   auto element_type = GetIRContext()->get_type_mgr()->GetType(element_type_id);
273   auto element_count = vector_type_instruction.GetSingleWordInOperand(1);
274 
275   // Collect a mapping, from type id to width, for scalar/vector types that are
276   // smaller in width than |vector_type|, but that have the same underlying
277   // type.  For example, if |vector_type| is vec4, the mapping will be:
278   //   { float -> 1, vec2 -> 2, vec3 -> 3 }
279   // The mapping will have missing entries if some of these types do not exist.
280 
281   std::map<uint32_t, uint32_t> smaller_vector_type_id_to_width;
282   // Add the underlying type.  This id must exist, in order for |vector_type| to
283   // exist.
284   smaller_vector_type_id_to_width[element_type_id] = 1;
285 
286   // Now add every vector type with width at least 2, and less than the width of
287   // |vector_type|.
288   for (uint32_t width = 2; width < element_count; width++) {
289     opt::analysis::Vector smaller_vector_type(element_type, width);
290     auto smaller_vector_type_id =
291         GetIRContext()->get_type_mgr()->GetId(&smaller_vector_type);
292     // We might find that there is no declared type of this smaller width.
293     // For example, a module can declare vec4 without having declared vec2 or
294     // vec3.
295     if (smaller_vector_type_id) {
296       smaller_vector_type_id_to_width[smaller_vector_type_id] = width;
297     }
298   }
299 
300   // Now we know the types that are available to us, we set about populating a
301   // vector of the right length.  We do this by deciding, with no order in mind,
302   // which instructions we will use to populate the vector, and subsequently
303   // randomly choosing an order.  This is to avoid biasing construction of
304   // vectors with smaller vectors to the left and scalars to the right.  That is
305   // a concern because, e.g. in the case of populating a vec4, if we populate
306   // the constructor instructions left-to-right, we can always choose a vec3 to
307   // construct the first three elements, but can only choose a vec3 to construct
308   // the last three elements if we chose a float to construct the first element
309   // (otherwise there will not be space left for a vec3).
310 
311   uint32_t vector_slots_used = 0;
312   // The instructions we will use to construct the vector, in no particular
313   // order at this stage.
314   std::vector<opt::Instruction*> instructions_to_use;
315 
316   while (vector_slots_used < element_count) {
317     std::vector<opt::Instruction*> instructions_to_choose_from;
318     for (auto& entry : smaller_vector_type_id_to_width) {
319       if (entry.second >
320           std::min(element_count - 1, element_count - vector_slots_used)) {
321         continue;
322       }
323       auto available_instructions =
324           type_id_to_available_instructions.find(entry.first);
325       if (available_instructions == type_id_to_available_instructions.cend()) {
326         continue;
327       }
328       instructions_to_choose_from.insert(instructions_to_choose_from.end(),
329                                          available_instructions->second.begin(),
330                                          available_instructions->second.end());
331     }
332     if (instructions_to_choose_from.empty()) {
333       // We may get unlucky and find that there are not any instructions to
334       // choose from.  In this case we give up constructing a composite of this
335       // vector type.  It might be that we could construct the composite in
336       // another manner, so we could opt to retry a few times here, but it is
337       // simpler to just give up on the basis that this will not happen
338       // frequently.
339       return {};
340     }
341     auto instruction_to_use =
342         instructions_to_choose_from[GetFuzzerContext()->RandomIndex(
343             instructions_to_choose_from)];
344     instructions_to_use.push_back(instruction_to_use);
345     auto chosen_type =
346         GetIRContext()->get_type_mgr()->GetType(instruction_to_use->type_id());
347     if (chosen_type->AsVector()) {
348       assert(chosen_type->AsVector()->element_type() == element_type);
349       assert(chosen_type->AsVector()->element_count() < element_count);
350       assert(chosen_type->AsVector()->element_count() <=
351              element_count - vector_slots_used);
352       vector_slots_used += chosen_type->AsVector()->element_count();
353     } else {
354       assert(chosen_type == element_type);
355       vector_slots_used += 1;
356     }
357   }
358   assert(vector_slots_used == element_count);
359 
360   std::vector<uint32_t> result;
361   std::vector<uint32_t> operands;
362   while (!instructions_to_use.empty()) {
363     auto index = GetFuzzerContext()->RandomIndex(instructions_to_use);
364     result.push_back(instructions_to_use[index]->result_id());
365     instructions_to_use.erase(instructions_to_use.begin() + index);
366   }
367   assert(result.size() > 1);
368   return result;
369 }
370 
371 }  // namespace fuzz
372 }  // namespace spvtools
373