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