1 // Copyright (c) 2020 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_add_equation_instructions.h"
16
17 #include <vector>
18
19 #include "source/fuzz/fuzzer_util.h"
20 #include "source/fuzz/transformation_equation_instruction.h"
21
22 namespace spvtools {
23 namespace fuzz {
24 namespace {
25
IsBitWidthSupported(opt::IRContext * ir_context,uint32_t bit_width)26 bool IsBitWidthSupported(opt::IRContext* ir_context, uint32_t bit_width) {
27 switch (bit_width) {
28 case 32:
29 return true;
30 case 64:
31 return ir_context->get_feature_mgr()->HasCapability(
32 spv::Capability::Float64) &&
33 ir_context->get_feature_mgr()->HasCapability(
34 spv::Capability::Int64);
35 case 16:
36 return ir_context->get_feature_mgr()->HasCapability(
37 spv::Capability::Float16) &&
38 ir_context->get_feature_mgr()->HasCapability(
39 spv::Capability::Int16);
40 default:
41 return false;
42 }
43 }
44
45 } // namespace
46
FuzzerPassAddEquationInstructions(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations,bool ignore_inapplicable_transformations)47 FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions(
48 opt::IRContext* ir_context, TransformationContext* transformation_context,
49 FuzzerContext* fuzzer_context,
50 protobufs::TransformationSequence* transformations,
51 bool ignore_inapplicable_transformations)
52 : FuzzerPass(ir_context, transformation_context, fuzzer_context,
53 transformations, ignore_inapplicable_transformations) {}
54
Apply()55 void FuzzerPassAddEquationInstructions::Apply() {
56 ForEachInstructionWithInstructionDescriptor(
57 [this](opt::Function* function, opt::BasicBlock* block,
58 opt::BasicBlock::iterator inst_it,
59 const protobufs::InstructionDescriptor& instruction_descriptor) {
60 if (!GetFuzzerContext()->ChoosePercentage(
61 GetFuzzerContext()->GetChanceOfAddingEquationInstruction())) {
62 return;
63 }
64
65 // Check that it is OK to add an equation instruction before the given
66 // instruction in principle - e.g. check that this does not lead to
67 // inserting before an OpVariable or OpPhi instruction. We use OpIAdd
68 // as an example opcode for this check, to be representative of *some*
69 // opcode that defines an equation, even though we may choose a
70 // different opcode below.
71 if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(spv::Op::OpIAdd,
72 inst_it)) {
73 return;
74 }
75
76 // Get all available instructions with result ids and types that are not
77 // OpUndef.
78 std::vector<opt::Instruction*> available_instructions =
79 FindAvailableInstructions(
80 function, block, inst_it,
81 [this](opt::IRContext* /*unused*/,
82 opt::Instruction* instruction) -> bool {
83 return instruction->result_id() && instruction->type_id() &&
84 instruction->opcode() != spv::Op::OpUndef &&
85 !GetTransformationContext()
86 ->GetFactManager()
87 ->IdIsIrrelevant(instruction->result_id());
88 });
89
90 // Try the opcodes for which we know how to make ids at random until
91 // something works.
92 std::vector<spv::Op> candidate_opcodes = {
93 spv::Op::OpIAdd, spv::Op::OpISub, spv::Op::OpLogicalNot,
94 spv::Op::OpSNegate, spv::Op::OpConvertUToF, spv::Op::OpConvertSToF,
95 spv::Op::OpBitcast};
96 do {
97 auto opcode =
98 GetFuzzerContext()->RemoveAtRandomIndex(&candidate_opcodes);
99 switch (opcode) {
100 case spv::Op::OpConvertSToF:
101 case spv::Op::OpConvertUToF: {
102 std::vector<const opt::Instruction*> candidate_instructions;
103 for (const auto* inst :
104 GetIntegerInstructions(available_instructions)) {
105 const auto* type =
106 GetIRContext()->get_type_mgr()->GetType(inst->type_id());
107 assert(type && "|inst| has invalid type");
108
109 if (const auto* vector_type = type->AsVector()) {
110 type = vector_type->element_type();
111 }
112
113 if (IsBitWidthSupported(GetIRContext(),
114 type->AsInteger()->width())) {
115 candidate_instructions.push_back(inst);
116 }
117 }
118
119 if (candidate_instructions.empty()) {
120 break;
121 }
122
123 const auto* operand =
124 candidate_instructions[GetFuzzerContext()->RandomIndex(
125 candidate_instructions)];
126
127 const auto* type =
128 GetIRContext()->get_type_mgr()->GetType(operand->type_id());
129 assert(type && "Operand has invalid type");
130
131 // Make sure a result type exists in the module.
132 if (const auto* vector = type->AsVector()) {
133 // We store element count in a separate variable since the
134 // call FindOrCreate* functions below might invalidate
135 // |vector| pointer.
136 const auto element_count = vector->element_count();
137
138 FindOrCreateVectorType(
139 FindOrCreateFloatType(
140 vector->element_type()->AsInteger()->width()),
141 element_count);
142 } else {
143 FindOrCreateFloatType(type->AsInteger()->width());
144 }
145
146 ApplyTransformation(TransformationEquationInstruction(
147 GetFuzzerContext()->GetFreshId(), opcode,
148 {operand->result_id()}, instruction_descriptor));
149 return;
150 }
151 case spv::Op::OpBitcast: {
152 const auto candidate_instructions =
153 GetNumericalInstructions(available_instructions);
154
155 if (!candidate_instructions.empty()) {
156 const auto* operand_inst =
157 candidate_instructions[GetFuzzerContext()->RandomIndex(
158 candidate_instructions)];
159 const auto* operand_type =
160 GetIRContext()->get_type_mgr()->GetType(
161 operand_inst->type_id());
162 assert(operand_type && "Operand instruction has invalid type");
163
164 // Make sure a result type exists in the module.
165 //
166 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3539):
167 // The only constraint on the types of OpBitcast's parameters
168 // is that they must have the same number of bits. Consider
169 // improving the code below to support this in full.
170 if (const auto* vector = operand_type->AsVector()) {
171 // We store element count in a separate variable since the
172 // call FindOrCreate* functions below might invalidate
173 // |vector| pointer.
174 const auto element_count = vector->element_count();
175
176 uint32_t element_type_id;
177 if (const auto* int_type =
178 vector->element_type()->AsInteger()) {
179 element_type_id = FindOrCreateFloatType(int_type->width());
180 } else {
181 assert(vector->element_type()->AsFloat() &&
182 "Vector must have numerical elements");
183 element_type_id = FindOrCreateIntegerType(
184 vector->element_type()->AsFloat()->width(),
185 GetFuzzerContext()->ChooseEven());
186 }
187
188 FindOrCreateVectorType(element_type_id, element_count);
189 } else if (const auto* int_type = operand_type->AsInteger()) {
190 FindOrCreateFloatType(int_type->width());
191 } else {
192 assert(operand_type->AsFloat() &&
193 "Operand is not a scalar of numerical type");
194 FindOrCreateIntegerType(operand_type->AsFloat()->width(),
195 GetFuzzerContext()->ChooseEven());
196 }
197
198 ApplyTransformation(TransformationEquationInstruction(
199 GetFuzzerContext()->GetFreshId(), opcode,
200 {operand_inst->result_id()}, instruction_descriptor));
201 return;
202 }
203 } break;
204 case spv::Op::OpIAdd:
205 case spv::Op::OpISub: {
206 // Instructions of integer (scalar or vector) result type are
207 // suitable for these opcodes.
208 auto integer_instructions =
209 GetIntegerInstructions(available_instructions);
210 if (!integer_instructions.empty()) {
211 // There is at least one such instruction, so pick one at random
212 // for the LHS of an equation.
213 auto lhs = integer_instructions.at(
214 GetFuzzerContext()->RandomIndex(integer_instructions));
215
216 // For the RHS, we can use any instruction with an integer
217 // scalar/vector result type of the same number of components
218 // and the same bit-width for the underlying integer type.
219
220 // Work out the element count and bit-width.
221 auto lhs_type =
222 GetIRContext()->get_type_mgr()->GetType(lhs->type_id());
223 uint32_t lhs_element_count;
224 uint32_t lhs_bit_width;
225 if (lhs_type->AsVector()) {
226 lhs_element_count = lhs_type->AsVector()->element_count();
227 lhs_bit_width = lhs_type->AsVector()
228 ->element_type()
229 ->AsInteger()
230 ->width();
231 } else {
232 lhs_element_count = 1;
233 lhs_bit_width = lhs_type->AsInteger()->width();
234 }
235
236 // Get all the instructions that match on element count and
237 // bit-width.
238 auto candidate_rhs_instructions = RestrictToElementBitWidth(
239 RestrictToVectorWidth(integer_instructions,
240 lhs_element_count),
241 lhs_bit_width);
242
243 // Choose a RHS instruction at random; there is guaranteed to
244 // be at least one choice as the LHS will be available.
245 auto rhs = candidate_rhs_instructions.at(
246 GetFuzzerContext()->RandomIndex(
247 candidate_rhs_instructions));
248
249 // Add the equation instruction.
250 ApplyTransformation(TransformationEquationInstruction(
251 GetFuzzerContext()->GetFreshId(), opcode,
252 {lhs->result_id(), rhs->result_id()},
253 instruction_descriptor));
254 return;
255 }
256 break;
257 }
258 case spv::Op::OpLogicalNot: {
259 // Choose any available instruction of boolean scalar/vector
260 // result type and equate its negation with a fresh id.
261 auto boolean_instructions =
262 GetBooleanInstructions(available_instructions);
263 if (!boolean_instructions.empty()) {
264 ApplyTransformation(TransformationEquationInstruction(
265 GetFuzzerContext()->GetFreshId(), opcode,
266 {boolean_instructions
267 .at(GetFuzzerContext()->RandomIndex(
268 boolean_instructions))
269 ->result_id()},
270 instruction_descriptor));
271 return;
272 }
273 break;
274 }
275 case spv::Op::OpSNegate: {
276 // Similar to OpLogicalNot, but for signed integer negation.
277 auto integer_instructions =
278 GetIntegerInstructions(available_instructions);
279 if (!integer_instructions.empty()) {
280 ApplyTransformation(TransformationEquationInstruction(
281 GetFuzzerContext()->GetFreshId(), opcode,
282 {integer_instructions
283 .at(GetFuzzerContext()->RandomIndex(
284 integer_instructions))
285 ->result_id()},
286 instruction_descriptor));
287 return;
288 }
289 break;
290 }
291 default:
292 assert(false && "Unexpected opcode.");
293 break;
294 }
295 } while (!candidate_opcodes.empty());
296 // Reaching here means that we did not manage to apply any
297 // transformation at this point of the module.
298 });
299 }
300
301 std::vector<opt::Instruction*>
GetIntegerInstructions(const std::vector<opt::Instruction * > & instructions) const302 FuzzerPassAddEquationInstructions::GetIntegerInstructions(
303 const std::vector<opt::Instruction*>& instructions) const {
304 std::vector<opt::Instruction*> result;
305 for (auto& inst : instructions) {
306 auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
307 if (type->AsInteger() ||
308 (type->AsVector() && type->AsVector()->element_type()->AsInteger())) {
309 result.push_back(inst);
310 }
311 }
312 return result;
313 }
314
315 std::vector<opt::Instruction*>
GetFloatInstructions(const std::vector<opt::Instruction * > & instructions) const316 FuzzerPassAddEquationInstructions::GetFloatInstructions(
317 const std::vector<opt::Instruction*>& instructions) const {
318 std::vector<opt::Instruction*> result;
319 for (auto& inst : instructions) {
320 auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
321 if (type->AsFloat() ||
322 (type->AsVector() && type->AsVector()->element_type()->AsFloat())) {
323 result.push_back(inst);
324 }
325 }
326 return result;
327 }
328
329 std::vector<opt::Instruction*>
GetBooleanInstructions(const std::vector<opt::Instruction * > & instructions) const330 FuzzerPassAddEquationInstructions::GetBooleanInstructions(
331 const std::vector<opt::Instruction*>& instructions) const {
332 std::vector<opt::Instruction*> result;
333 for (auto& inst : instructions) {
334 auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
335 if (type->AsBool() ||
336 (type->AsVector() && type->AsVector()->element_type()->AsBool())) {
337 result.push_back(inst);
338 }
339 }
340 return result;
341 }
342
343 std::vector<opt::Instruction*>
RestrictToVectorWidth(const std::vector<opt::Instruction * > & instructions,uint32_t vector_width) const344 FuzzerPassAddEquationInstructions::RestrictToVectorWidth(
345 const std::vector<opt::Instruction*>& instructions,
346 uint32_t vector_width) const {
347 std::vector<opt::Instruction*> result;
348 for (auto& inst : instructions) {
349 auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
350 // Get the vector width of |inst|, which is 1 if |inst| is a scalar and is
351 // otherwise derived from its vector type.
352 uint32_t other_vector_width =
353 type->AsVector() ? type->AsVector()->element_count() : 1;
354 // Keep |inst| if the vector widths match.
355 if (vector_width == other_vector_width) {
356 result.push_back(inst);
357 }
358 }
359 return result;
360 }
361
362 std::vector<opt::Instruction*>
RestrictToElementBitWidth(const std::vector<opt::Instruction * > & instructions,uint32_t bit_width) const363 FuzzerPassAddEquationInstructions::RestrictToElementBitWidth(
364 const std::vector<opt::Instruction*>& instructions,
365 uint32_t bit_width) const {
366 std::vector<opt::Instruction*> result;
367 for (auto& inst : instructions) {
368 const opt::analysis::Type* type =
369 GetIRContext()->get_type_mgr()->GetType(inst->type_id());
370 if (type->AsVector()) {
371 type = type->AsVector()->element_type();
372 }
373 assert((type->AsInteger() || type->AsFloat()) &&
374 "Precondition: all input instructions must "
375 "have integer or float scalar or vector type.");
376 if ((type->AsInteger() && type->AsInteger()->width() == bit_width) ||
377 (type->AsFloat() && type->AsFloat()->width() == bit_width)) {
378 result.push_back(inst);
379 }
380 }
381 return result;
382 }
383
384 std::vector<opt::Instruction*>
GetNumericalInstructions(const std::vector<opt::Instruction * > & instructions) const385 FuzzerPassAddEquationInstructions::GetNumericalInstructions(
386 const std::vector<opt::Instruction*>& instructions) const {
387 std::vector<opt::Instruction*> result;
388
389 for (auto* inst : instructions) {
390 const auto* type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
391 assert(type && "Instruction has invalid type");
392
393 if (const auto* vector_type = type->AsVector()) {
394 type = vector_type->element_type();
395 }
396
397 if (!type->AsInteger() && !type->AsFloat()) {
398 // Only numerical scalars or vectors of numerical components are
399 // supported.
400 continue;
401 }
402
403 if (!IsBitWidthSupported(GetIRContext(), type->AsInteger()
404 ? type->AsInteger()->width()
405 : type->AsFloat()->width())) {
406 continue;
407 }
408
409 result.push_back(inst);
410 }
411
412 return result;
413 }
414
415 } // namespace fuzz
416 } // namespace spvtools
417