1 // Copyright (c) 2020 Vasyl Teliman
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/transformation_add_synonym.h"
16
17 #include <utility>
18
19 #include "source/fuzz/fuzzer_util.h"
20 #include "source/fuzz/instruction_descriptor.h"
21
22 namespace spvtools {
23 namespace fuzz {
24
TransformationAddSynonym(protobufs::TransformationAddSynonym message)25 TransformationAddSynonym::TransformationAddSynonym(
26 protobufs::TransformationAddSynonym message)
27 : message_(std::move(message)) {}
28
TransformationAddSynonym(uint32_t result_id,protobufs::TransformationAddSynonym::SynonymType synonym_type,uint32_t synonym_fresh_id,const protobufs::InstructionDescriptor & insert_before)29 TransformationAddSynonym::TransformationAddSynonym(
30 uint32_t result_id,
31 protobufs::TransformationAddSynonym::SynonymType synonym_type,
32 uint32_t synonym_fresh_id,
33 const protobufs::InstructionDescriptor& insert_before) {
34 message_.set_result_id(result_id);
35 message_.set_synonym_type(synonym_type);
36 message_.set_synonym_fresh_id(synonym_fresh_id);
37 *message_.mutable_insert_before() = insert_before;
38 }
39
IsApplicable(opt::IRContext * ir_context,const TransformationContext & transformation_context) const40 bool TransformationAddSynonym::IsApplicable(
41 opt::IRContext* ir_context,
42 const TransformationContext& transformation_context) const {
43 assert(protobufs::TransformationAddSynonym::SynonymType_IsValid(
44 message_.synonym_type()) &&
45 "Synonym type is invalid");
46
47 // |synonym_fresh_id| must be fresh.
48 if (!fuzzerutil::IsFreshId(ir_context, message_.synonym_fresh_id())) {
49 return false;
50 }
51
52 // Check that |message_.result_id| is valid.
53 auto* synonym = ir_context->get_def_use_mgr()->GetDef(message_.result_id());
54 if (!synonym) {
55 return false;
56 }
57
58 // Check that we can apply |synonym_type| to |result_id|.
59 if (!IsInstructionValid(ir_context, transformation_context, synonym,
60 message_.synonym_type())) {
61 return false;
62 }
63
64 // Check that |insert_before| is valid.
65 auto* insert_before_inst =
66 FindInstruction(message_.insert_before(), ir_context);
67 if (!insert_before_inst) {
68 return false;
69 }
70
71 const auto* insert_before_inst_block =
72 ir_context->get_instr_block(insert_before_inst);
73 assert(insert_before_inst_block &&
74 "|insert_before_inst| must be in some block");
75
76 if (transformation_context.GetFactManager()->BlockIsDead(
77 insert_before_inst_block->id())) {
78 // We don't create synonyms in dead blocks.
79 return false;
80 }
81
82 // Check that we can insert |message._synonymous_instruction| before
83 // |message_.insert_before| instruction. We use OpIAdd to represent some
84 // instruction that can produce a synonym.
85 if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(spv::Op::OpIAdd,
86 insert_before_inst)) {
87 return false;
88 }
89
90 // A constant instruction must be present in the module if required.
91 if (IsAdditionalConstantRequired(message_.synonym_type()) &&
92 MaybeGetConstantId(ir_context, transformation_context) == 0) {
93 return false;
94 }
95
96 // Domination rules must be satisfied.
97 return fuzzerutil::IdIsAvailableBeforeInstruction(
98 ir_context, insert_before_inst, message_.result_id());
99 }
100
Apply(opt::IRContext * ir_context,TransformationContext * transformation_context) const101 void TransformationAddSynonym::Apply(
102 opt::IRContext* ir_context,
103 TransformationContext* transformation_context) const {
104 // Add a synonymous instruction.
105 auto new_instruction =
106 MakeSynonymousInstruction(ir_context, *transformation_context);
107 auto new_instruction_ptr = new_instruction.get();
108 auto insert_before = FindInstruction(message_.insert_before(), ir_context);
109 insert_before->InsertBefore(std::move(new_instruction));
110
111 fuzzerutil::UpdateModuleIdBound(ir_context, message_.synonym_fresh_id());
112
113 // Inform the def-use manager about the new instruction and record its basic
114 // block.
115 ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr);
116 ir_context->set_instr_block(new_instruction_ptr,
117 ir_context->get_instr_block(insert_before));
118
119 // Propagate PointeeValueIsIrrelevant fact.
120 const auto* new_synonym_type = ir_context->get_type_mgr()->GetType(
121 fuzzerutil::GetTypeId(ir_context, message_.synonym_fresh_id()));
122 assert(new_synonym_type && "New synonym should have a valid type");
123
124 if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
125 message_.result_id()) &&
126 new_synonym_type->AsPointer()) {
127 transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
128 message_.synonym_fresh_id());
129 }
130
131 // Mark two ids as synonymous.
132 transformation_context->GetFactManager()->AddFactDataSynonym(
133 MakeDataDescriptor(message_.result_id(), {}),
134 MakeDataDescriptor(message_.synonym_fresh_id(), {}));
135 }
136
ToMessage() const137 protobufs::Transformation TransformationAddSynonym::ToMessage() const {
138 protobufs::Transformation result;
139 *result.mutable_add_synonym() = message_;
140 return result;
141 }
142
IsInstructionValid(opt::IRContext * ir_context,const TransformationContext & transformation_context,opt::Instruction * inst,protobufs::TransformationAddSynonym::SynonymType synonym_type)143 bool TransformationAddSynonym::IsInstructionValid(
144 opt::IRContext* ir_context,
145 const TransformationContext& transformation_context, opt::Instruction* inst,
146 protobufs::TransformationAddSynonym::SynonymType synonym_type) {
147 // Instruction must have a result id, type id. We skip OpUndef and
148 // OpConstantNull.
149 if (!inst || !inst->result_id() || !inst->type_id() ||
150 inst->opcode() == spv::Op::OpUndef ||
151 inst->opcode() == spv::Op::OpConstantNull) {
152 return false;
153 }
154
155 if (!fuzzerutil::CanMakeSynonymOf(ir_context, transformation_context,
156 *inst)) {
157 return false;
158 }
159
160 switch (synonym_type) {
161 case protobufs::TransformationAddSynonym::ADD_ZERO:
162 case protobufs::TransformationAddSynonym::SUB_ZERO:
163 case protobufs::TransformationAddSynonym::MUL_ONE: {
164 // The instruction must be either scalar or vector of integers or floats.
165 const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id());
166 assert(type && "Instruction's result id is invalid");
167
168 if (const auto* vector = type->AsVector()) {
169 return vector->element_type()->AsInteger() ||
170 vector->element_type()->AsFloat();
171 }
172
173 return type->AsInteger() || type->AsFloat();
174 }
175 case protobufs::TransformationAddSynonym::BITWISE_OR:
176 case protobufs::TransformationAddSynonym::BITWISE_XOR: {
177 // The instruction must be either an integer or a vector of integers.
178 const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id());
179 assert(type && "Instruction's result id is invalid");
180
181 if (const auto* vector = type->AsVector()) {
182 return vector->element_type()->AsInteger();
183 }
184
185 return type->AsInteger();
186 }
187 case protobufs::TransformationAddSynonym::COPY_OBJECT:
188 // All checks for OpCopyObject are handled by
189 // fuzzerutil::CanMakeSynonymOf.
190 return true;
191 case protobufs::TransformationAddSynonym::LOGICAL_AND:
192 case protobufs::TransformationAddSynonym::LOGICAL_OR: {
193 // The instruction must be either a scalar or a vector of booleans.
194 const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id());
195 assert(type && "Instruction's result id is invalid");
196 return (type->AsVector() && type->AsVector()->element_type()->AsBool()) ||
197 type->AsBool();
198 }
199 default:
200 assert(false && "Synonym type is not supported");
201 return false;
202 }
203 }
204
205 std::unique_ptr<opt::Instruction>
MakeSynonymousInstruction(opt::IRContext * ir_context,const TransformationContext & transformation_context) const206 TransformationAddSynonym::MakeSynonymousInstruction(
207 opt::IRContext* ir_context,
208 const TransformationContext& transformation_context) const {
209 auto synonym_type_id =
210 fuzzerutil::GetTypeId(ir_context, message_.result_id());
211 assert(synonym_type_id && "Synonym has invalid type id");
212 auto opcode = spv::Op::OpNop;
213 const auto* synonym_type =
214 ir_context->get_type_mgr()->GetType(synonym_type_id);
215 assert(synonym_type && "Synonym has invalid type");
216
217 auto is_integral = (synonym_type->AsVector() &&
218 synonym_type->AsVector()->element_type()->AsInteger()) ||
219 synonym_type->AsInteger();
220
221 switch (message_.synonym_type()) {
222 case protobufs::TransformationAddSynonym::SUB_ZERO:
223 opcode = is_integral ? spv::Op::OpISub : spv::Op::OpFSub;
224 break;
225 case protobufs::TransformationAddSynonym::MUL_ONE:
226 opcode = is_integral ? spv::Op::OpIMul : spv::Op::OpFMul;
227 break;
228 case protobufs::TransformationAddSynonym::ADD_ZERO:
229 opcode = is_integral ? spv::Op::OpIAdd : spv::Op::OpFAdd;
230 break;
231 case protobufs::TransformationAddSynonym::LOGICAL_OR:
232 opcode = spv::Op::OpLogicalOr;
233 break;
234 case protobufs::TransformationAddSynonym::LOGICAL_AND:
235 opcode = spv::Op::OpLogicalAnd;
236 break;
237 case protobufs::TransformationAddSynonym::BITWISE_OR:
238 opcode = spv::Op::OpBitwiseOr;
239 break;
240 case protobufs::TransformationAddSynonym::BITWISE_XOR:
241 opcode = spv::Op::OpBitwiseXor;
242 break;
243
244 case protobufs::TransformationAddSynonym::COPY_OBJECT:
245 return MakeUnique<opt::Instruction>(
246 ir_context, spv::Op::OpCopyObject, synonym_type_id,
247 message_.synonym_fresh_id(),
248 opt::Instruction::OperandList{
249 {SPV_OPERAND_TYPE_ID, {message_.result_id()}}});
250
251 default:
252 assert(false && "Unhandled synonym type");
253 return nullptr;
254 }
255
256 return MakeUnique<opt::Instruction>(
257 ir_context, opcode, synonym_type_id, message_.synonym_fresh_id(),
258 opt::Instruction::OperandList{
259 {SPV_OPERAND_TYPE_ID, {message_.result_id()}},
260 {SPV_OPERAND_TYPE_ID,
261 {MaybeGetConstantId(ir_context, transformation_context)}}});
262 }
263
MaybeGetConstantId(opt::IRContext * ir_context,const TransformationContext & transformation_context) const264 uint32_t TransformationAddSynonym::MaybeGetConstantId(
265 opt::IRContext* ir_context,
266 const TransformationContext& transformation_context) const {
267 assert(IsAdditionalConstantRequired(message_.synonym_type()) &&
268 "Synonym type doesn't require an additional constant");
269
270 auto synonym_type_id =
271 fuzzerutil::GetTypeId(ir_context, message_.result_id());
272 assert(synonym_type_id && "Synonym has invalid type id");
273
274 switch (message_.synonym_type()) {
275 case protobufs::TransformationAddSynonym::ADD_ZERO:
276 case protobufs::TransformationAddSynonym::SUB_ZERO:
277 case protobufs::TransformationAddSynonym::LOGICAL_OR:
278 case protobufs::TransformationAddSynonym::BITWISE_OR:
279 case protobufs::TransformationAddSynonym::BITWISE_XOR:
280 return fuzzerutil::MaybeGetZeroConstant(
281 ir_context, transformation_context, synonym_type_id, false);
282 case protobufs::TransformationAddSynonym::MUL_ONE:
283 case protobufs::TransformationAddSynonym::LOGICAL_AND: {
284 auto synonym_type = ir_context->get_type_mgr()->GetType(synonym_type_id);
285 assert(synonym_type && "Synonym has invalid type");
286
287 if (const auto* vector = synonym_type->AsVector()) {
288 auto element_type_id =
289 ir_context->get_type_mgr()->GetId(vector->element_type());
290 assert(element_type_id && "Vector's element type is invalid");
291
292 auto one_word =
293 vector->element_type()->AsFloat() ? fuzzerutil::FloatToWord(1) : 1u;
294 if (auto scalar_one_id = fuzzerutil::MaybeGetScalarConstant(
295 ir_context, transformation_context, {one_word}, element_type_id,
296 false)) {
297 return fuzzerutil::MaybeGetCompositeConstant(
298 ir_context, transformation_context,
299 std::vector<uint32_t>(vector->element_count(), scalar_one_id),
300 synonym_type_id, false);
301 }
302
303 return 0;
304 } else {
305 return fuzzerutil::MaybeGetScalarConstant(
306 ir_context, transformation_context,
307 {synonym_type->AsFloat() ? fuzzerutil::FloatToWord(1) : 1u},
308 synonym_type_id, false);
309 }
310 }
311 default:
312 // The assertion at the beginning of the function will fail in the debug
313 // mode.
314 return 0;
315 }
316 }
317
IsAdditionalConstantRequired(protobufs::TransformationAddSynonym::SynonymType synonym_type)318 bool TransformationAddSynonym::IsAdditionalConstantRequired(
319 protobufs::TransformationAddSynonym::SynonymType synonym_type) {
320 switch (synonym_type) {
321 case protobufs::TransformationAddSynonym::ADD_ZERO:
322 case protobufs::TransformationAddSynonym::SUB_ZERO:
323 case protobufs::TransformationAddSynonym::LOGICAL_OR:
324 case protobufs::TransformationAddSynonym::MUL_ONE:
325 case protobufs::TransformationAddSynonym::LOGICAL_AND:
326 case protobufs::TransformationAddSynonym::BITWISE_OR:
327 case protobufs::TransformationAddSynonym::BITWISE_XOR:
328 return true;
329 default:
330 return false;
331 }
332 }
333
GetFreshIds() const334 std::unordered_set<uint32_t> TransformationAddSynonym::GetFreshIds() const {
335 return {message_.synonym_fresh_id()};
336 }
337
338 } // namespace fuzz
339 } // namespace spvtools
340