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