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_propagate_instruction_down.h"
16
17 #include "source/fuzz/fuzzer_util.h"
18 #include "source/fuzz/instruction_descriptor.h"
19
20 namespace spvtools {
21 namespace fuzz {
22
TransformationPropagateInstructionDown(const protobufs::TransformationPropagateInstructionDown & message)23 TransformationPropagateInstructionDown::TransformationPropagateInstructionDown(
24 const protobufs::TransformationPropagateInstructionDown& message)
25 : message_(message) {}
26
TransformationPropagateInstructionDown(uint32_t block_id,uint32_t phi_fresh_id,const std::map<uint32_t,uint32_t> & successor_id_to_fresh_id)27 TransformationPropagateInstructionDown::TransformationPropagateInstructionDown(
28 uint32_t block_id, uint32_t phi_fresh_id,
29 const std::map<uint32_t, uint32_t>& successor_id_to_fresh_id) {
30 message_.set_block_id(block_id);
31 message_.set_phi_fresh_id(phi_fresh_id);
32 *message_.mutable_successor_id_to_fresh_id() =
33 fuzzerutil::MapToRepeatedUInt32Pair(successor_id_to_fresh_id);
34 }
35
IsApplicable(opt::IRContext * ir_context,const TransformationContext & transformation_context) const36 bool TransformationPropagateInstructionDown::IsApplicable(
37 opt::IRContext* ir_context,
38 const TransformationContext& transformation_context) const {
39 // Check that we can apply this transformation to the |block_id|.
40 if (!IsApplicableToBlock(ir_context, message_.block_id())) {
41 return false;
42 }
43
44 const auto successor_id_to_fresh_id =
45 fuzzerutil::RepeatedUInt32PairToMap(message_.successor_id_to_fresh_id());
46
47 for (auto id : GetAcceptableSuccessors(ir_context, message_.block_id())) {
48 // Each successor must have a fresh id in the |successor_id_to_fresh_id|
49 // map, unless overflow ids are available.
50 if (!successor_id_to_fresh_id.count(id) &&
51 !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
52 return false;
53 }
54 }
55
56 std::vector<uint32_t> maybe_fresh_ids = {message_.phi_fresh_id()};
57 maybe_fresh_ids.reserve(successor_id_to_fresh_id.size());
58 for (const auto& entry : successor_id_to_fresh_id) {
59 maybe_fresh_ids.push_back(entry.second);
60 }
61
62 // All ids must be unique and fresh.
63 return !fuzzerutil::HasDuplicates(maybe_fresh_ids) &&
64 std::all_of(maybe_fresh_ids.begin(), maybe_fresh_ids.end(),
65 [ir_context](uint32_t id) {
66 return fuzzerutil::IsFreshId(ir_context, id);
67 });
68 }
69
Apply(opt::IRContext * ir_context,TransformationContext * transformation_context) const70 void TransformationPropagateInstructionDown::Apply(
71 opt::IRContext* ir_context,
72 TransformationContext* transformation_context) const {
73 // Get instruction to propagate down. There must be one.
74 auto* inst_to_propagate =
75 GetInstructionToPropagate(ir_context, message_.block_id());
76 assert(inst_to_propagate && "There must be an instruction to propagate");
77
78 auto successor_id_to_fresh_id =
79 fuzzerutil::RepeatedUInt32PairToMap(message_.successor_id_to_fresh_id());
80 std::vector<uint32_t> created_inst_ids;
81 auto successor_ids = GetAcceptableSuccessors(ir_context, message_.block_id());
82
83 // Clone |inst_to_propagate| into every successor.
84 for (auto successor_id : successor_ids) {
85 std::unique_ptr<opt::Instruction> clone(
86 inst_to_propagate->Clone(ir_context));
87
88 uint32_t new_result_id;
89 if (successor_id_to_fresh_id.count(successor_id)) {
90 new_result_id = successor_id_to_fresh_id.at(successor_id);
91 } else {
92 assert(transformation_context->GetOverflowIdSource()->HasOverflowIds() &&
93 "Overflow ids must be available");
94 new_result_id =
95 transformation_context->GetOverflowIdSource()->GetNextOverflowId();
96 successor_id_to_fresh_id[successor_id] = new_result_id;
97 }
98
99 clone->SetResultId(new_result_id);
100 fuzzerutil::UpdateModuleIdBound(ir_context, new_result_id);
101
102 auto* insert_before_inst = GetFirstInsertBeforeInstruction(
103 ir_context, successor_id, clone->opcode());
104 assert(insert_before_inst && "Can't insert into one of the successors");
105
106 insert_before_inst->InsertBefore(std::move(clone));
107 created_inst_ids.push_back(new_result_id);
108 }
109
110 // Add an OpPhi instruction into the module if possible.
111 if (auto merge_block_id = GetOpPhiBlockId(
112 ir_context, message_.block_id(), *inst_to_propagate, successor_ids)) {
113 opt::Instruction::OperandList in_operands;
114 std::unordered_set<uint32_t> visited_predecessors;
115 for (auto predecessor_id : ir_context->cfg()->preds(merge_block_id)) {
116 if (visited_predecessors.count(predecessor_id)) {
117 // Merge block might have multiple identical predecessors.
118 continue;
119 }
120
121 visited_predecessors.insert(predecessor_id);
122
123 const auto* dominator_analysis = ir_context->GetDominatorAnalysis(
124 ir_context->cfg()->block(message_.block_id())->GetParent());
125
126 // Find the successor of |source_block| that dominates the predecessor of
127 // the merge block |predecessor_id|.
128 auto it = std::find_if(
129 successor_ids.begin(), successor_ids.end(),
130 [predecessor_id, dominator_analysis](uint32_t successor_id) {
131 return dominator_analysis->Dominates(successor_id, predecessor_id);
132 });
133
134 // OpPhi requires a single operand pair for every predecessor of the
135 // OpPhi's block.
136 assert(it != successor_ids.end() && "Unable to insert OpPhi");
137
138 in_operands.push_back(
139 {SPV_OPERAND_TYPE_ID, {successor_id_to_fresh_id.at(*it)}});
140 in_operands.push_back({SPV_OPERAND_TYPE_ID, {predecessor_id}});
141 }
142
143 ir_context->cfg()
144 ->block(merge_block_id)
145 ->begin()
146 ->InsertBefore(MakeUnique<opt::Instruction>(
147 ir_context, SpvOpPhi, inst_to_propagate->type_id(),
148 message_.phi_fresh_id(), std::move(in_operands)));
149
150 fuzzerutil::UpdateModuleIdBound(ir_context, message_.phi_fresh_id());
151 created_inst_ids.push_back(message_.phi_fresh_id());
152 }
153
154 // Make sure analyses are updated when we adjust users of |inst_to_propagate|.
155 ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
156
157 // Copy decorations from the original instructions to its propagated copies.
158 for (auto id : created_inst_ids) {
159 ir_context->get_decoration_mgr()->CloneDecorations(
160 inst_to_propagate->result_id(), id);
161 }
162
163 // Remove all decorations from the original instruction.
164 ir_context->get_decoration_mgr()->RemoveDecorationsFrom(
165 inst_to_propagate->result_id());
166
167 // Update every use of the |inst_to_propagate| with a result id of some of the
168 // newly created instructions.
169 ir_context->get_def_use_mgr()->ForEachUse(
170 inst_to_propagate, [ir_context, &created_inst_ids](
171 opt::Instruction* user, uint32_t operand_index) {
172 assert(ir_context->get_instr_block(user) &&
173 "All decorations should have already been adjusted");
174
175 auto in_operand_index =
176 fuzzerutil::InOperandIndexFromOperandIndex(*user, operand_index);
177 for (auto id : created_inst_ids) {
178 if (fuzzerutil::IdIsAvailableAtUse(ir_context, user, in_operand_index,
179 id)) {
180 user->SetInOperand(in_operand_index, {id});
181 return;
182 }
183 }
184
185 // Every user of |inst_to_propagate| must be updated since we will
186 // remove that instruction from the module.
187 assert(false && "Every user of |inst_to_propagate| must be updated");
188 });
189
190 // Add synonyms about newly created instructions.
191 assert(inst_to_propagate->HasResultId() &&
192 "Result id is required to add facts");
193 if (transformation_context->GetFactManager()->IdIsIrrelevant(
194 inst_to_propagate->result_id())) {
195 for (auto id : created_inst_ids) {
196 transformation_context->GetFactManager()->AddFactIdIsIrrelevant(id);
197 }
198 } else {
199 std::vector<uint32_t> non_irrelevant_ids;
200 for (auto id : created_inst_ids) {
201 // |id| can be irrelevant implicitly (e.g. if we propagate it into a dead
202 // block).
203 if (!transformation_context->GetFactManager()->IdIsIrrelevant(id)) {
204 non_irrelevant_ids.push_back(id);
205 }
206 }
207
208 if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
209 inst_to_propagate->result_id())) {
210 for (auto id : non_irrelevant_ids) {
211 transformation_context->GetFactManager()
212 ->AddFactValueOfPointeeIsIrrelevant(id);
213 }
214 }
215
216 for (auto id : non_irrelevant_ids) {
217 transformation_context->GetFactManager()->AddFactDataSynonym(
218 MakeDataDescriptor(id, {}),
219 MakeDataDescriptor(non_irrelevant_ids[0], {}));
220 }
221 }
222
223 // Remove the propagated instruction from the module.
224 ir_context->KillInst(inst_to_propagate);
225
226 // We've adjusted all users - make sure these changes are analyzed.
227 ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
228 }
229
ToMessage() const230 protobufs::Transformation TransformationPropagateInstructionDown::ToMessage()
231 const {
232 protobufs::Transformation result;
233 *result.mutable_propagate_instruction_down() = message_;
234 return result;
235 }
236
IsOpcodeSupported(SpvOp opcode)237 bool TransformationPropagateInstructionDown::IsOpcodeSupported(SpvOp opcode) {
238 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605):
239 // We only support "simple" instructions that don't work with memory.
240 // We should extend this so that we support the ones that modify the memory
241 // too.
242 switch (opcode) {
243 case SpvOpUndef:
244 case SpvOpAccessChain:
245 case SpvOpInBoundsAccessChain:
246 case SpvOpArrayLength:
247 case SpvOpVectorExtractDynamic:
248 case SpvOpVectorInsertDynamic:
249 case SpvOpVectorShuffle:
250 case SpvOpCompositeConstruct:
251 case SpvOpCompositeExtract:
252 case SpvOpCompositeInsert:
253 case SpvOpCopyObject:
254 case SpvOpTranspose:
255 case SpvOpConvertFToU:
256 case SpvOpConvertFToS:
257 case SpvOpConvertSToF:
258 case SpvOpConvertUToF:
259 case SpvOpUConvert:
260 case SpvOpSConvert:
261 case SpvOpFConvert:
262 case SpvOpQuantizeToF16:
263 case SpvOpSatConvertSToU:
264 case SpvOpSatConvertUToS:
265 case SpvOpBitcast:
266 case SpvOpSNegate:
267 case SpvOpFNegate:
268 case SpvOpIAdd:
269 case SpvOpFAdd:
270 case SpvOpISub:
271 case SpvOpFSub:
272 case SpvOpIMul:
273 case SpvOpFMul:
274 case SpvOpUDiv:
275 case SpvOpSDiv:
276 case SpvOpFDiv:
277 case SpvOpUMod:
278 case SpvOpSRem:
279 case SpvOpSMod:
280 case SpvOpFRem:
281 case SpvOpFMod:
282 case SpvOpVectorTimesScalar:
283 case SpvOpMatrixTimesScalar:
284 case SpvOpVectorTimesMatrix:
285 case SpvOpMatrixTimesVector:
286 case SpvOpMatrixTimesMatrix:
287 case SpvOpOuterProduct:
288 case SpvOpDot:
289 case SpvOpIAddCarry:
290 case SpvOpISubBorrow:
291 case SpvOpUMulExtended:
292 case SpvOpSMulExtended:
293 case SpvOpAny:
294 case SpvOpAll:
295 case SpvOpIsNan:
296 case SpvOpIsInf:
297 case SpvOpIsFinite:
298 case SpvOpIsNormal:
299 case SpvOpSignBitSet:
300 case SpvOpLessOrGreater:
301 case SpvOpOrdered:
302 case SpvOpUnordered:
303 case SpvOpLogicalEqual:
304 case SpvOpLogicalNotEqual:
305 case SpvOpLogicalOr:
306 case SpvOpLogicalAnd:
307 case SpvOpLogicalNot:
308 case SpvOpSelect:
309 case SpvOpIEqual:
310 case SpvOpINotEqual:
311 case SpvOpUGreaterThan:
312 case SpvOpSGreaterThan:
313 case SpvOpUGreaterThanEqual:
314 case SpvOpSGreaterThanEqual:
315 case SpvOpULessThan:
316 case SpvOpSLessThan:
317 case SpvOpULessThanEqual:
318 case SpvOpSLessThanEqual:
319 case SpvOpFOrdEqual:
320 case SpvOpFUnordEqual:
321 case SpvOpFOrdNotEqual:
322 case SpvOpFUnordNotEqual:
323 case SpvOpFOrdLessThan:
324 case SpvOpFUnordLessThan:
325 case SpvOpFOrdGreaterThan:
326 case SpvOpFUnordGreaterThan:
327 case SpvOpFOrdLessThanEqual:
328 case SpvOpFUnordLessThanEqual:
329 case SpvOpFOrdGreaterThanEqual:
330 case SpvOpFUnordGreaterThanEqual:
331 case SpvOpShiftRightLogical:
332 case SpvOpShiftRightArithmetic:
333 case SpvOpShiftLeftLogical:
334 case SpvOpBitwiseOr:
335 case SpvOpBitwiseXor:
336 case SpvOpBitwiseAnd:
337 case SpvOpNot:
338 case SpvOpBitFieldInsert:
339 case SpvOpBitFieldSExtract:
340 case SpvOpBitFieldUExtract:
341 case SpvOpBitReverse:
342 case SpvOpBitCount:
343 case SpvOpCopyLogical:
344 case SpvOpPtrEqual:
345 case SpvOpPtrNotEqual:
346 return true;
347 default:
348 return false;
349 }
350 }
351
352 opt::Instruction*
GetInstructionToPropagate(opt::IRContext * ir_context,uint32_t block_id)353 TransformationPropagateInstructionDown::GetInstructionToPropagate(
354 opt::IRContext* ir_context, uint32_t block_id) {
355 auto* block = ir_context->cfg()->block(block_id);
356 assert(block && "|block_id| is invalid");
357
358 for (auto it = block->rbegin(); it != block->rend(); ++it) {
359 if (!it->result_id() || !it->type_id() ||
360 !IsOpcodeSupported(it->opcode())) {
361 continue;
362 }
363
364 auto all_users_from_different_blocks =
365 ir_context->get_def_use_mgr()->WhileEachUser(
366 &*it, [ir_context, block](opt::Instruction* user) {
367 return ir_context->get_instr_block(user) != block;
368 });
369
370 if (!all_users_from_different_blocks) {
371 // We can't propagate an instruction if it's used in the same block.
372 continue;
373 }
374
375 return &*it;
376 }
377
378 return nullptr;
379 }
380
IsApplicableToBlock(opt::IRContext * ir_context,uint32_t block_id)381 bool TransformationPropagateInstructionDown::IsApplicableToBlock(
382 opt::IRContext* ir_context, uint32_t block_id) {
383 // Check that |block_id| is valid.
384 const auto* block = fuzzerutil::MaybeFindBlock(ir_context, block_id);
385 if (!block) {
386 return false;
387 }
388
389 const auto* dominator_analysis =
390 ir_context->GetDominatorAnalysis(block->GetParent());
391
392 // |block| must be reachable.
393 if (!dominator_analysis->IsReachable(block)) {
394 return false;
395 }
396
397 // The block must have an instruction to propagate.
398 const auto* inst_to_propagate =
399 GetInstructionToPropagate(ir_context, block_id);
400 if (!inst_to_propagate) {
401 return false;
402 }
403
404 // Check that |block| has successors.
405 auto successor_ids = GetAcceptableSuccessors(ir_context, block_id);
406 if (successor_ids.empty()) {
407 return false;
408 }
409
410 // Check that |successor_block| doesn't have any OpPhi instructions that
411 // use |inst|.
412 for (auto successor_id : successor_ids) {
413 for (const auto& maybe_phi_inst : *ir_context->cfg()->block(successor_id)) {
414 if (maybe_phi_inst.opcode() != SpvOpPhi) {
415 // OpPhis can be intermixed with OpLine and OpNoLine.
416 continue;
417 }
418
419 for (uint32_t i = 0; i < maybe_phi_inst.NumInOperands(); i += 2) {
420 if (maybe_phi_inst.GetSingleWordInOperand(i) ==
421 inst_to_propagate->result_id()) {
422 return false;
423 }
424 }
425 }
426 }
427
428 // Get the result id of the block we will insert OpPhi instruction into.
429 // This is either 0 or a result id of some merge block in the function.
430 auto phi_block_id =
431 GetOpPhiBlockId(ir_context, block_id, *inst_to_propagate, successor_ids);
432
433 // Make sure we can adjust all users of the propagated instruction.
434 return ir_context->get_def_use_mgr()->WhileEachUse(
435 inst_to_propagate,
436 [ir_context, &successor_ids, dominator_analysis, phi_block_id](
437 opt::Instruction* user, uint32_t index) {
438 const auto* user_block = ir_context->get_instr_block(user);
439
440 if (!user_block) {
441 // |user| might be a global instruction (e.g. OpDecorate).
442 return true;
443 }
444
445 // Check that at least one of the ids in |successor_ids| or a
446 // |phi_block_id| dominates |user|'s block (or its predecessor if the
447 // user is an OpPhi). We can't use fuzzerutil::IdIsAvailableAtUse since
448 // the id in question hasn't yet been created in the module.
449 auto block_id_to_dominate = user->opcode() == SpvOpPhi
450 ? user->GetSingleWordOperand(index + 1)
451 : user_block->id();
452
453 if (phi_block_id != 0 &&
454 dominator_analysis->Dominates(phi_block_id, block_id_to_dominate)) {
455 return true;
456 }
457
458 return std::any_of(
459 successor_ids.begin(), successor_ids.end(),
460 [dominator_analysis, block_id_to_dominate](uint32_t id) {
461 return dominator_analysis->Dominates(id, block_id_to_dominate);
462 });
463 });
464 }
465
466 opt::Instruction*
GetFirstInsertBeforeInstruction(opt::IRContext * ir_context,uint32_t block_id,SpvOp opcode)467 TransformationPropagateInstructionDown::GetFirstInsertBeforeInstruction(
468 opt::IRContext* ir_context, uint32_t block_id, SpvOp opcode) {
469 auto* block = ir_context->cfg()->block(block_id);
470
471 auto it = block->begin();
472
473 while (it != block->end() &&
474 !fuzzerutil::CanInsertOpcodeBeforeInstruction(opcode, it)) {
475 ++it;
476 }
477
478 return it == block->end() ? nullptr : &*it;
479 }
480
481 std::unordered_set<uint32_t>
GetAcceptableSuccessors(opt::IRContext * ir_context,uint32_t block_id)482 TransformationPropagateInstructionDown::GetAcceptableSuccessors(
483 opt::IRContext* ir_context, uint32_t block_id) {
484 const auto* block = ir_context->cfg()->block(block_id);
485 assert(block && "|block_id| is invalid");
486
487 const auto* inst = GetInstructionToPropagate(ir_context, block_id);
488 assert(inst && "The block must have an instruction to propagate");
489
490 std::unordered_set<uint32_t> result;
491 block->ForEachSuccessorLabel([ir_context, &result,
492 inst](uint32_t successor_id) {
493 if (result.count(successor_id)) {
494 return;
495 }
496
497 auto* successor_block = ir_context->cfg()->block(successor_id);
498
499 // We can't propagate |inst| into |successor_block| if the latter is not
500 // dominated by the |inst|'s dependencies.
501 if (!inst->WhileEachInId([ir_context, successor_block](const uint32_t* id) {
502 return fuzzerutil::IdIsAvailableBeforeInstruction(
503 ir_context, &*successor_block->begin(), *id);
504 })) {
505 return;
506 }
507
508 // We don't propagate any "special" instructions (e.g. OpSelectionMerge
509 // etc), thus, insertion point must always exist if the module is valid.
510 assert(GetFirstInsertBeforeInstruction(ir_context, successor_id,
511 inst->opcode()) &&
512 "There must exist an insertion point.");
513
514 result.insert(successor_id);
515 });
516
517 return result;
518 }
519
GetOpPhiBlockId(opt::IRContext * ir_context,uint32_t block_id,const opt::Instruction & inst_to_propagate,const std::unordered_set<uint32_t> & successor_ids)520 uint32_t TransformationPropagateInstructionDown::GetOpPhiBlockId(
521 opt::IRContext* ir_context, uint32_t block_id,
522 const opt::Instruction& inst_to_propagate,
523 const std::unordered_set<uint32_t>& successor_ids) {
524 const auto* block = ir_context->cfg()->block(block_id);
525
526 // |block_id| must belong to some construct.
527 auto merge_block_id =
528 block->GetMergeInst()
529 ? block->GetMergeInst()->GetSingleWordInOperand(0)
530 : ir_context->GetStructuredCFGAnalysis()->MergeBlock(block_id);
531 if (!merge_block_id) {
532 return 0;
533 }
534
535 const auto* dominator_analysis =
536 ir_context->GetDominatorAnalysis(block->GetParent());
537
538 // Check that |merge_block_id| is reachable in the CFG and |block_id|
539 // dominates |merge_block_id|.
540 if (!dominator_analysis->IsReachable(merge_block_id) ||
541 !dominator_analysis->Dominates(block_id, merge_block_id)) {
542 return 0;
543 }
544
545 // We can't insert an OpPhi into |merge_block_id| if it's an acceptable
546 // successor of |block_id|.
547 if (successor_ids.count(merge_block_id)) {
548 return 0;
549 }
550
551 // All predecessors of the merge block must be dominated by at least one
552 // successor of the |block_id|.
553 assert(!ir_context->cfg()->preds(merge_block_id).empty() &&
554 "Merge block must be reachable");
555 for (auto predecessor_id : ir_context->cfg()->preds(merge_block_id)) {
556 if (std::none_of(
557 successor_ids.begin(), successor_ids.end(),
558 [dominator_analysis, predecessor_id](uint32_t successor_id) {
559 return dominator_analysis->Dominates(successor_id,
560 predecessor_id);
561 })) {
562 return 0;
563 }
564 }
565
566 const auto* propagate_type =
567 ir_context->get_type_mgr()->GetType(inst_to_propagate.type_id());
568 assert(propagate_type && "|inst_to_propagate| must have a valid type");
569
570 // VariablePointers capability implicitly declares
571 // VariablePointersStorageBuffer. We need those capabilities since otherwise
572 // OpPhi instructions cannot have operands of pointer types.
573 if (propagate_type->AsPointer() &&
574 !ir_context->get_feature_mgr()->HasCapability(
575 SpvCapabilityVariablePointersStorageBuffer)) {
576 return 0;
577 }
578
579 return merge_block_id;
580 }
581
582 std::unordered_set<uint32_t>
GetFreshIds() const583 TransformationPropagateInstructionDown::GetFreshIds() const {
584 std::unordered_set<uint32_t> result = {message_.phi_fresh_id()};
585 for (const auto& pair : message_.successor_id_to_fresh_id()) {
586 result.insert(pair.second());
587 }
588 return result;
589 }
590
591 } // namespace fuzz
592 } // namespace spvtools
593