• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
FuzzerPassAddEquationInstructions(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations)25 FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions(
26     opt::IRContext* ir_context, TransformationContext* transformation_context,
27     FuzzerContext* fuzzer_context,
28     protobufs::TransformationSequence* transformations)
29     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
30                  transformations) {}
31 
32 FuzzerPassAddEquationInstructions::~FuzzerPassAddEquationInstructions() =
33     default;
34 
Apply()35 void FuzzerPassAddEquationInstructions::Apply() {
36   ForEachInstructionWithInstructionDescriptor(
37       [this](opt::Function* function, opt::BasicBlock* block,
38              opt::BasicBlock::iterator inst_it,
39              const protobufs::InstructionDescriptor& instruction_descriptor) {
40         if (!GetFuzzerContext()->ChoosePercentage(
41                 GetFuzzerContext()->GetChanceOfAddingEquationInstruction())) {
42           return;
43         }
44 
45         // Check that it is OK to add an equation instruction before the given
46         // instruction in principle - e.g. check that this does not lead to
47         // inserting before an OpVariable or OpPhi instruction.  We use OpIAdd
48         // as an example opcode for this check, to be representative of *some*
49         // opcode that defines an equation, even though we may choose a
50         // different opcode below.
51         if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpIAdd, inst_it)) {
52           return;
53         }
54 
55         // Get all available instructions with result ids and types that are not
56         // OpUndef.
57         std::vector<opt::Instruction*> available_instructions =
58             FindAvailableInstructions(
59                 function, block, inst_it,
60                 [](opt::IRContext*, opt::Instruction* instruction) -> bool {
61                   return instruction->result_id() && instruction->type_id() &&
62                          instruction->opcode() != SpvOpUndef;
63                 });
64 
65         // Try the opcodes for which we know how to make ids at random until
66         // something works.
67         std::vector<SpvOp> candidate_opcodes = {SpvOpIAdd, SpvOpISub,
68                                                 SpvOpLogicalNot, SpvOpSNegate};
69         do {
70           auto opcode =
71               GetFuzzerContext()->RemoveAtRandomIndex(&candidate_opcodes);
72           switch (opcode) {
73             case SpvOpIAdd:
74             case SpvOpISub: {
75               // Instructions of integer (scalar or vector) result type are
76               // suitable for these opcodes.
77               auto integer_instructions =
78                   GetIntegerInstructions(available_instructions);
79               if (!integer_instructions.empty()) {
80                 // There is at least one such instruction, so pick one at random
81                 // for the LHS of an equation.
82                 auto lhs = integer_instructions.at(
83                     GetFuzzerContext()->RandomIndex(integer_instructions));
84 
85                 // For the RHS, we can use any instruction with an integer
86                 // scalar/vector result type of the same number of components
87                 // and the same bit-width for the underlying integer type.
88 
89                 // Work out the element count and bit-width.
90                 auto lhs_type =
91                     GetIRContext()->get_type_mgr()->GetType(lhs->type_id());
92                 uint32_t lhs_element_count;
93                 uint32_t lhs_bit_width;
94                 if (lhs_type->AsVector()) {
95                   lhs_element_count = lhs_type->AsVector()->element_count();
96                   lhs_bit_width = lhs_type->AsVector()
97                                       ->element_type()
98                                       ->AsInteger()
99                                       ->width();
100                 } else {
101                   lhs_element_count = 1;
102                   lhs_bit_width = lhs_type->AsInteger()->width();
103                 }
104 
105                 // Get all the instructions that match on element count and
106                 // bit-width.
107                 auto candidate_rhs_instructions = RestrictToElementBitWidth(
108                     RestrictToVectorWidth(integer_instructions,
109                                           lhs_element_count),
110                     lhs_bit_width);
111 
112                 // Choose a RHS instruction at random; there is guaranteed to
113                 // be at least one choice as the LHS will be available.
114                 auto rhs = candidate_rhs_instructions.at(
115                     GetFuzzerContext()->RandomIndex(
116                         candidate_rhs_instructions));
117 
118                 // Add the equation instruction.
119                 ApplyTransformation(TransformationEquationInstruction(
120                     GetFuzzerContext()->GetFreshId(), opcode,
121                     {lhs->result_id(), rhs->result_id()},
122                     instruction_descriptor));
123                 return;
124               }
125               break;
126             }
127             case SpvOpLogicalNot: {
128               // Choose any available instruction of boolean scalar/vector
129               // result type and equate its negation with a fresh id.
130               auto boolean_instructions =
131                   GetBooleanInstructions(available_instructions);
132               if (!boolean_instructions.empty()) {
133                 ApplyTransformation(TransformationEquationInstruction(
134                     GetFuzzerContext()->GetFreshId(), opcode,
135                     {boolean_instructions
136                          .at(GetFuzzerContext()->RandomIndex(
137                              boolean_instructions))
138                          ->result_id()},
139                     instruction_descriptor));
140                 return;
141               }
142               break;
143             }
144             case SpvOpSNegate: {
145               // Similar to OpLogicalNot, but for signed integer negation.
146               auto integer_instructions =
147                   GetIntegerInstructions(available_instructions);
148               if (!integer_instructions.empty()) {
149                 ApplyTransformation(TransformationEquationInstruction(
150                     GetFuzzerContext()->GetFreshId(), opcode,
151                     {integer_instructions
152                          .at(GetFuzzerContext()->RandomIndex(
153                              integer_instructions))
154                          ->result_id()},
155                     instruction_descriptor));
156                 return;
157               }
158               break;
159             }
160             default:
161               assert(false && "Unexpected opcode.");
162               break;
163           }
164         } while (!candidate_opcodes.empty());
165         // Reaching here means that we did not manage to apply any
166         // transformation at this point of the module.
167       });
168 }
169 
170 std::vector<opt::Instruction*>
GetIntegerInstructions(const std::vector<opt::Instruction * > & instructions) const171 FuzzerPassAddEquationInstructions::GetIntegerInstructions(
172     const std::vector<opt::Instruction*>& instructions) const {
173   std::vector<opt::Instruction*> result;
174   for (auto& inst : instructions) {
175     auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
176     if (type->AsInteger() ||
177         (type->AsVector() && type->AsVector()->element_type()->AsInteger())) {
178       result.push_back(inst);
179     }
180   }
181   return result;
182 }
183 
184 std::vector<opt::Instruction*>
GetBooleanInstructions(const std::vector<opt::Instruction * > & instructions) const185 FuzzerPassAddEquationInstructions::GetBooleanInstructions(
186     const std::vector<opt::Instruction*>& instructions) const {
187   std::vector<opt::Instruction*> result;
188   for (auto& inst : instructions) {
189     auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
190     if (type->AsBool() ||
191         (type->AsVector() && type->AsVector()->element_type()->AsBool())) {
192       result.push_back(inst);
193     }
194   }
195   return result;
196 }
197 
198 std::vector<opt::Instruction*>
RestrictToVectorWidth(const std::vector<opt::Instruction * > & instructions,uint32_t vector_width) const199 FuzzerPassAddEquationInstructions::RestrictToVectorWidth(
200     const std::vector<opt::Instruction*>& instructions,
201     uint32_t vector_width) const {
202   std::vector<opt::Instruction*> result;
203   for (auto& inst : instructions) {
204     auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
205     // Get the vector width of |inst|, which is 1 if |inst| is a scalar and is
206     // otherwise derived from its vector type.
207     uint32_t other_vector_width =
208         type->AsVector() ? type->AsVector()->element_count() : 1;
209     // Keep |inst| if the vector widths match.
210     if (vector_width == other_vector_width) {
211       result.push_back(inst);
212     }
213   }
214   return result;
215 }
216 
217 std::vector<opt::Instruction*>
RestrictToElementBitWidth(const std::vector<opt::Instruction * > & instructions,uint32_t bit_width) const218 FuzzerPassAddEquationInstructions::RestrictToElementBitWidth(
219     const std::vector<opt::Instruction*>& instructions,
220     uint32_t bit_width) const {
221   std::vector<opt::Instruction*> result;
222   for (auto& inst : instructions) {
223     const opt::analysis::Type* type =
224         GetIRContext()->get_type_mgr()->GetType(inst->type_id());
225     if (type->AsVector()) {
226       type = type->AsVector()->element_type();
227     }
228     assert(type->AsInteger() &&
229            "Precondition: all input instructions must "
230            "have integer scalar or vector type.");
231     if (type->AsInteger()->width() == bit_width) {
232       result.push_back(inst);
233     }
234   }
235   return result;
236 }
237 
238 }  // namespace fuzz
239 }  // namespace spvtools
240