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