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