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.h"
16
17 #include <set>
18
19 #include "source/fuzz/fuzzer_util.h"
20 #include "source/fuzz/instruction_descriptor.h"
21 #include "source/fuzz/transformation_add_constant_boolean.h"
22 #include "source/fuzz/transformation_add_constant_composite.h"
23 #include "source/fuzz/transformation_add_constant_scalar.h"
24 #include "source/fuzz/transformation_add_global_undef.h"
25 #include "source/fuzz/transformation_add_type_boolean.h"
26 #include "source/fuzz/transformation_add_type_float.h"
27 #include "source/fuzz/transformation_add_type_function.h"
28 #include "source/fuzz/transformation_add_type_int.h"
29 #include "source/fuzz/transformation_add_type_matrix.h"
30 #include "source/fuzz/transformation_add_type_pointer.h"
31 #include "source/fuzz/transformation_add_type_vector.h"
32
33 namespace spvtools {
34 namespace fuzz {
35
FuzzerPass(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations)36 FuzzerPass::FuzzerPass(opt::IRContext* ir_context,
37 TransformationContext* transformation_context,
38 FuzzerContext* fuzzer_context,
39 protobufs::TransformationSequence* transformations)
40 : ir_context_(ir_context),
41 transformation_context_(transformation_context),
42 fuzzer_context_(fuzzer_context),
43 transformations_(transformations) {}
44
45 FuzzerPass::~FuzzerPass() = default;
46
FindAvailableInstructions(opt::Function * function,opt::BasicBlock * block,const opt::BasicBlock::iterator & inst_it,std::function<bool (opt::IRContext *,opt::Instruction *)> instruction_is_relevant) const47 std::vector<opt::Instruction*> FuzzerPass::FindAvailableInstructions(
48 opt::Function* function, opt::BasicBlock* block,
49 const opt::BasicBlock::iterator& inst_it,
50 std::function<bool(opt::IRContext*, opt::Instruction*)>
51 instruction_is_relevant) const {
52 // TODO(afd) The following is (relatively) simple, but may end up being
53 // prohibitively inefficient, as it walks the whole dominator tree for
54 // every instruction that is considered.
55
56 std::vector<opt::Instruction*> result;
57 // Consider all global declarations
58 for (auto& global : GetIRContext()->module()->types_values()) {
59 if (instruction_is_relevant(GetIRContext(), &global)) {
60 result.push_back(&global);
61 }
62 }
63
64 // Consider all function parameters
65 function->ForEachParam(
66 [this, &instruction_is_relevant, &result](opt::Instruction* param) {
67 if (instruction_is_relevant(GetIRContext(), param)) {
68 result.push_back(param);
69 }
70 });
71
72 // Consider all previous instructions in this block
73 for (auto prev_inst_it = block->begin(); prev_inst_it != inst_it;
74 ++prev_inst_it) {
75 if (instruction_is_relevant(GetIRContext(), &*prev_inst_it)) {
76 result.push_back(&*prev_inst_it);
77 }
78 }
79
80 // Walk the dominator tree to consider all instructions from dominating
81 // blocks
82 auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function);
83 for (auto next_dominator = dominator_analysis->ImmediateDominator(block);
84 next_dominator != nullptr;
85 next_dominator =
86 dominator_analysis->ImmediateDominator(next_dominator)) {
87 for (auto& dominating_inst : *next_dominator) {
88 if (instruction_is_relevant(GetIRContext(), &dominating_inst)) {
89 result.push_back(&dominating_inst);
90 }
91 }
92 }
93 return result;
94 }
95
ForEachInstructionWithInstructionDescriptor(std::function<void (opt::Function * function,opt::BasicBlock * block,opt::BasicBlock::iterator inst_it,const protobufs::InstructionDescriptor & instruction_descriptor)> action)96 void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
97 std::function<
98 void(opt::Function* function, opt::BasicBlock* block,
99 opt::BasicBlock::iterator inst_it,
100 const protobufs::InstructionDescriptor& instruction_descriptor)>
101 action) {
102 // Consider every block in every function.
103 for (auto& function : *GetIRContext()->module()) {
104 for (auto& block : function) {
105 // We now consider every instruction in the block, randomly deciding
106 // whether to apply a transformation before it.
107
108 // In order for transformations to insert new instructions, they need to
109 // be able to identify the instruction to insert before. We describe an
110 // instruction via its opcode, 'opc', a base instruction 'base' that has a
111 // result id, and the number of instructions with opcode 'opc' that we
112 // should skip when searching from 'base' for the desired instruction.
113 // (An instruction that has a result id is represented by its own opcode,
114 // itself as 'base', and a skip-count of 0.)
115 std::vector<std::tuple<uint32_t, SpvOp, uint32_t>>
116 base_opcode_skip_triples;
117
118 // The initial base instruction is the block label.
119 uint32_t base = block.id();
120
121 // Counts the number of times we have seen each opcode since we reset the
122 // base instruction.
123 std::map<SpvOp, uint32_t> skip_count;
124
125 // Consider every instruction in the block. The label is excluded: it is
126 // only necessary to consider it as a base in case the first instruction
127 // in the block does not have a result id.
128 for (auto inst_it = block.begin(); inst_it != block.end(); ++inst_it) {
129 if (inst_it->HasResultId()) {
130 // In the case that the instruction has a result id, we use the
131 // instruction as its own base, and clear the skip counts we have
132 // collected.
133 base = inst_it->result_id();
134 skip_count.clear();
135 }
136 const SpvOp opcode = inst_it->opcode();
137
138 // Invoke the provided function, which might apply a transformation.
139 action(&function, &block, inst_it,
140 MakeInstructionDescriptor(
141 base, opcode,
142 skip_count.count(opcode) ? skip_count.at(opcode) : 0));
143
144 if (!inst_it->HasResultId()) {
145 skip_count[opcode] =
146 skip_count.count(opcode) ? skip_count.at(opcode) + 1 : 1;
147 }
148 }
149 }
150 }
151 }
152
FindOrCreateBoolType()153 uint32_t FuzzerPass::FindOrCreateBoolType() {
154 opt::analysis::Bool bool_type;
155 auto existing_id = GetIRContext()->get_type_mgr()->GetId(&bool_type);
156 if (existing_id) {
157 return existing_id;
158 }
159 auto result = GetFuzzerContext()->GetFreshId();
160 ApplyTransformation(TransformationAddTypeBoolean(result));
161 return result;
162 }
163
FindOrCreate32BitIntegerType(bool is_signed)164 uint32_t FuzzerPass::FindOrCreate32BitIntegerType(bool is_signed) {
165 opt::analysis::Integer int_type(32, is_signed);
166 auto existing_id = GetIRContext()->get_type_mgr()->GetId(&int_type);
167 if (existing_id) {
168 return existing_id;
169 }
170 auto result = GetFuzzerContext()->GetFreshId();
171 ApplyTransformation(TransformationAddTypeInt(result, 32, is_signed));
172 return result;
173 }
174
FindOrCreate32BitFloatType()175 uint32_t FuzzerPass::FindOrCreate32BitFloatType() {
176 opt::analysis::Float float_type(32);
177 auto existing_id = GetIRContext()->get_type_mgr()->GetId(&float_type);
178 if (existing_id) {
179 return existing_id;
180 }
181 auto result = GetFuzzerContext()->GetFreshId();
182 ApplyTransformation(TransformationAddTypeFloat(result, 32));
183 return result;
184 }
185
FindOrCreateFunctionType(uint32_t return_type_id,const std::vector<uint32_t> & argument_id)186 uint32_t FuzzerPass::FindOrCreateFunctionType(
187 uint32_t return_type_id, const std::vector<uint32_t>& argument_id) {
188 // FindFunctionType has a sigle argument for OpTypeFunction operands
189 // so we will have to copy them all in this vector
190 std::vector<uint32_t> type_ids(argument_id.size() + 1);
191 type_ids[0] = return_type_id;
192 std::copy(argument_id.begin(), argument_id.end(), type_ids.begin() + 1);
193
194 // Check if type exists
195 auto existing_id = fuzzerutil::FindFunctionType(GetIRContext(), type_ids);
196 if (existing_id) {
197 return existing_id;
198 }
199
200 auto result = GetFuzzerContext()->GetFreshId();
201 ApplyTransformation(
202 TransformationAddTypeFunction(result, return_type_id, argument_id));
203 return result;
204 }
205
FindOrCreateVectorType(uint32_t component_type_id,uint32_t component_count)206 uint32_t FuzzerPass::FindOrCreateVectorType(uint32_t component_type_id,
207 uint32_t component_count) {
208 assert(component_count >= 2 && component_count <= 4 &&
209 "Precondition: component count must be in range [2, 4].");
210 opt::analysis::Type* component_type =
211 GetIRContext()->get_type_mgr()->GetType(component_type_id);
212 assert(component_type && "Precondition: the component type must exist.");
213 opt::analysis::Vector vector_type(component_type, component_count);
214 auto existing_id = GetIRContext()->get_type_mgr()->GetId(&vector_type);
215 if (existing_id) {
216 return existing_id;
217 }
218 auto result = GetFuzzerContext()->GetFreshId();
219 ApplyTransformation(
220 TransformationAddTypeVector(result, component_type_id, component_count));
221 return result;
222 }
223
FindOrCreateMatrixType(uint32_t column_count,uint32_t row_count)224 uint32_t FuzzerPass::FindOrCreateMatrixType(uint32_t column_count,
225 uint32_t row_count) {
226 assert(column_count >= 2 && column_count <= 4 &&
227 "Precondition: column count must be in range [2, 4].");
228 assert(row_count >= 2 && row_count <= 4 &&
229 "Precondition: row count must be in range [2, 4].");
230 uint32_t column_type_id =
231 FindOrCreateVectorType(FindOrCreate32BitFloatType(), row_count);
232 opt::analysis::Type* column_type =
233 GetIRContext()->get_type_mgr()->GetType(column_type_id);
234 opt::analysis::Matrix matrix_type(column_type, column_count);
235 auto existing_id = GetIRContext()->get_type_mgr()->GetId(&matrix_type);
236 if (existing_id) {
237 return existing_id;
238 }
239 auto result = GetFuzzerContext()->GetFreshId();
240 ApplyTransformation(
241 TransformationAddTypeMatrix(result, column_type_id, column_count));
242 return result;
243 }
244
FindOrCreatePointerType(uint32_t base_type_id,SpvStorageClass storage_class)245 uint32_t FuzzerPass::FindOrCreatePointerType(uint32_t base_type_id,
246 SpvStorageClass storage_class) {
247 // We do not use the type manager here, due to problems related to isomorphic
248 // but distinct structs not being regarded as different.
249 auto existing_id = fuzzerutil::MaybeGetPointerType(
250 GetIRContext(), base_type_id, storage_class);
251 if (existing_id) {
252 return existing_id;
253 }
254 auto result = GetFuzzerContext()->GetFreshId();
255 ApplyTransformation(
256 TransformationAddTypePointer(result, storage_class, base_type_id));
257 return result;
258 }
259
FindOrCreatePointerTo32BitIntegerType(bool is_signed,SpvStorageClass storage_class)260 uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType(
261 bool is_signed, SpvStorageClass storage_class) {
262 return FindOrCreatePointerType(FindOrCreate32BitIntegerType(is_signed),
263 storage_class);
264 }
265
FindOrCreate32BitIntegerConstant(uint32_t word,bool is_signed)266 uint32_t FuzzerPass::FindOrCreate32BitIntegerConstant(uint32_t word,
267 bool is_signed) {
268 auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed);
269 opt::analysis::IntConstant int_constant(
270 GetIRContext()->get_type_mgr()->GetType(uint32_type_id)->AsInteger(),
271 {word});
272 auto existing_constant =
273 GetIRContext()->get_constant_mgr()->FindConstant(&int_constant);
274 if (existing_constant) {
275 return GetIRContext()
276 ->get_constant_mgr()
277 ->GetDefiningInstruction(existing_constant)
278 ->result_id();
279 }
280 auto result = GetFuzzerContext()->GetFreshId();
281 ApplyTransformation(
282 TransformationAddConstantScalar(result, uint32_type_id, {word}));
283 return result;
284 }
285
FindOrCreate32BitFloatConstant(uint32_t word)286 uint32_t FuzzerPass::FindOrCreate32BitFloatConstant(uint32_t word) {
287 auto float_type_id = FindOrCreate32BitFloatType();
288 opt::analysis::FloatConstant float_constant(
289 GetIRContext()->get_type_mgr()->GetType(float_type_id)->AsFloat(),
290 {word});
291 auto existing_constant =
292 GetIRContext()->get_constant_mgr()->FindConstant(&float_constant);
293 if (existing_constant) {
294 return GetIRContext()
295 ->get_constant_mgr()
296 ->GetDefiningInstruction(existing_constant)
297 ->result_id();
298 }
299 auto result = GetFuzzerContext()->GetFreshId();
300 ApplyTransformation(
301 TransformationAddConstantScalar(result, float_type_id, {word}));
302 return result;
303 }
304
FindOrCreateBoolConstant(bool value)305 uint32_t FuzzerPass::FindOrCreateBoolConstant(bool value) {
306 auto bool_type_id = FindOrCreateBoolType();
307 opt::analysis::BoolConstant bool_constant(
308 GetIRContext()->get_type_mgr()->GetType(bool_type_id)->AsBool(), value);
309 auto existing_constant =
310 GetIRContext()->get_constant_mgr()->FindConstant(&bool_constant);
311 if (existing_constant) {
312 return GetIRContext()
313 ->get_constant_mgr()
314 ->GetDefiningInstruction(existing_constant)
315 ->result_id();
316 }
317 auto result = GetFuzzerContext()->GetFreshId();
318 ApplyTransformation(TransformationAddConstantBoolean(result, value));
319 return result;
320 }
321
FindOrCreateGlobalUndef(uint32_t type_id)322 uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) {
323 for (auto& inst : GetIRContext()->types_values()) {
324 if (inst.opcode() == SpvOpUndef && inst.type_id() == type_id) {
325 return inst.result_id();
326 }
327 }
328 auto result = GetFuzzerContext()->GetFreshId();
329 ApplyTransformation(TransformationAddGlobalUndef(result, type_id));
330 return result;
331 }
332
333 std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
GetAvailableBasicTypesAndPointers(SpvStorageClass storage_class) const334 FuzzerPass::GetAvailableBasicTypesAndPointers(
335 SpvStorageClass storage_class) const {
336 // Records all of the basic types available in the module.
337 std::set<uint32_t> basic_types;
338
339 // For each basic type, records all the associated pointer types that target
340 // the basic type and that have |storage_class| as their storage class.
341 std::map<uint32_t, std::vector<uint32_t>> basic_type_to_pointers;
342
343 for (auto& inst : GetIRContext()->types_values()) {
344 // For each basic type that we come across, record type, and the fact that
345 // we cannot yet have seen any pointers that use the basic type as its
346 // pointee type.
347 //
348 // For pointer types with basic pointee types, associate the pointer type
349 // with the basic type.
350 switch (inst.opcode()) {
351 case SpvOpTypeBool:
352 case SpvOpTypeFloat:
353 case SpvOpTypeInt:
354 case SpvOpTypeMatrix:
355 case SpvOpTypeVector:
356 // These are all basic types.
357 basic_types.insert(inst.result_id());
358 basic_type_to_pointers.insert({inst.result_id(), {}});
359 break;
360 case SpvOpTypeArray:
361 // An array type is basic if its base type is basic.
362 if (basic_types.count(inst.GetSingleWordInOperand(0))) {
363 basic_types.insert(inst.result_id());
364 basic_type_to_pointers.insert({inst.result_id(), {}});
365 }
366 break;
367 case SpvOpTypeStruct: {
368 // A struct type is basic if all of its members are basic.
369 bool all_members_are_basic_types = true;
370 for (uint32_t i = 0; i < inst.NumInOperands(); i++) {
371 if (!basic_types.count(inst.GetSingleWordInOperand(i))) {
372 all_members_are_basic_types = false;
373 break;
374 }
375 }
376 if (all_members_are_basic_types) {
377 basic_types.insert(inst.result_id());
378 basic_type_to_pointers.insert({inst.result_id(), {}});
379 }
380 break;
381 }
382 case SpvOpTypePointer: {
383 // We are interested in the pointer if its pointee type is basic and it
384 // has the right storage class.
385 auto pointee_type = inst.GetSingleWordInOperand(1);
386 if (inst.GetSingleWordInOperand(0) == storage_class &&
387 basic_types.count(pointee_type)) {
388 // The pointer has the desired storage class, and its pointee type is
389 // a basic type, so we are interested in it. Associate it with its
390 // basic type.
391 basic_type_to_pointers.at(pointee_type).push_back(inst.result_id());
392 }
393 break;
394 }
395 default:
396 break;
397 }
398 }
399 return {{basic_types.begin(), basic_types.end()}, basic_type_to_pointers};
400 }
401
FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id)402 uint32_t FuzzerPass::FindOrCreateZeroConstant(
403 uint32_t scalar_or_composite_type_id) {
404 auto type_instruction =
405 GetIRContext()->get_def_use_mgr()->GetDef(scalar_or_composite_type_id);
406 assert(type_instruction && "The type instruction must exist.");
407 switch (type_instruction->opcode()) {
408 case SpvOpTypeBool:
409 return FindOrCreateBoolConstant(false);
410 case SpvOpTypeFloat:
411 return FindOrCreate32BitFloatConstant(0);
412 case SpvOpTypeInt:
413 return FindOrCreate32BitIntegerConstant(
414 0, type_instruction->GetSingleWordInOperand(1) != 0);
415 case SpvOpTypeArray: {
416 return GetZeroConstantForHomogeneousComposite(
417 *type_instruction, type_instruction->GetSingleWordInOperand(0),
418 fuzzerutil::GetArraySize(*type_instruction, GetIRContext()));
419 }
420 case SpvOpTypeMatrix:
421 case SpvOpTypeVector: {
422 return GetZeroConstantForHomogeneousComposite(
423 *type_instruction, type_instruction->GetSingleWordInOperand(0),
424 type_instruction->GetSingleWordInOperand(1));
425 }
426 case SpvOpTypeStruct: {
427 std::vector<const opt::analysis::Constant*> field_zero_constants;
428 std::vector<uint32_t> field_zero_ids;
429 for (uint32_t index = 0; index < type_instruction->NumInOperands();
430 index++) {
431 uint32_t field_constant_id = FindOrCreateZeroConstant(
432 type_instruction->GetSingleWordInOperand(index));
433 field_zero_ids.push_back(field_constant_id);
434 field_zero_constants.push_back(
435 GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
436 field_constant_id));
437 }
438 return FindOrCreateCompositeConstant(
439 *type_instruction, field_zero_constants, field_zero_ids);
440 }
441 default:
442 assert(false && "Unknown type.");
443 return 0;
444 }
445 }
446
FindOrCreateCompositeConstant(const opt::Instruction & composite_type_instruction,const std::vector<const opt::analysis::Constant * > & constants,const std::vector<uint32_t> & constant_ids)447 uint32_t FuzzerPass::FindOrCreateCompositeConstant(
448 const opt::Instruction& composite_type_instruction,
449 const std::vector<const opt::analysis::Constant*>& constants,
450 const std::vector<uint32_t>& constant_ids) {
451 assert(constants.size() == constant_ids.size() &&
452 "Precondition: |constants| and |constant_ids| must be in "
453 "correspondence.");
454
455 opt::analysis::Type* composite_type = GetIRContext()->get_type_mgr()->GetType(
456 composite_type_instruction.result_id());
457 std::unique_ptr<opt::analysis::Constant> composite_constant;
458 if (composite_type->AsArray()) {
459 composite_constant = MakeUnique<opt::analysis::ArrayConstant>(
460 composite_type->AsArray(), constants);
461 } else if (composite_type->AsMatrix()) {
462 composite_constant = MakeUnique<opt::analysis::MatrixConstant>(
463 composite_type->AsMatrix(), constants);
464 } else if (composite_type->AsStruct()) {
465 composite_constant = MakeUnique<opt::analysis::StructConstant>(
466 composite_type->AsStruct(), constants);
467 } else if (composite_type->AsVector()) {
468 composite_constant = MakeUnique<opt::analysis::VectorConstant>(
469 composite_type->AsVector(), constants);
470 } else {
471 assert(false &&
472 "Precondition: |composite_type| must declare a composite type.");
473 return 0;
474 }
475
476 uint32_t existing_constant =
477 GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
478 composite_constant.get(), composite_type_instruction.result_id());
479 if (existing_constant) {
480 return existing_constant;
481 }
482 uint32_t result = GetFuzzerContext()->GetFreshId();
483 ApplyTransformation(TransformationAddConstantComposite(
484 result, composite_type_instruction.result_id(), constant_ids));
485 return result;
486 }
487
GetZeroConstantForHomogeneousComposite(const opt::Instruction & composite_type_instruction,uint32_t component_type_id,uint32_t num_components)488 uint32_t FuzzerPass::GetZeroConstantForHomogeneousComposite(
489 const opt::Instruction& composite_type_instruction,
490 uint32_t component_type_id, uint32_t num_components) {
491 std::vector<const opt::analysis::Constant*> zero_constants;
492 std::vector<uint32_t> zero_ids;
493 uint32_t zero_component = FindOrCreateZeroConstant(component_type_id);
494 const opt::analysis::Constant* registered_zero_component =
495 GetIRContext()->get_constant_mgr()->FindDeclaredConstant(zero_component);
496 for (uint32_t i = 0; i < num_components; i++) {
497 zero_constants.push_back(registered_zero_component);
498 zero_ids.push_back(zero_component);
499 }
500 return FindOrCreateCompositeConstant(composite_type_instruction,
501 zero_constants, zero_ids);
502 }
503
504 } // namespace fuzz
505 } // namespace spvtools
506