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