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/transformation_flatten_conditional_branch.h"
16
17 #include "source/fuzz/fuzzer_util.h"
18 #include "source/fuzz/instruction_descriptor.h"
19
20 namespace spvtools {
21 namespace fuzz {
22
TransformationFlattenConditionalBranch(protobufs::TransformationFlattenConditionalBranch message)23 TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch(
24 protobufs::TransformationFlattenConditionalBranch message)
25 : message_(std::move(message)) {}
26
TransformationFlattenConditionalBranch(uint32_t header_block_id,bool true_branch_first,uint32_t fresh_id_for_bvec2_selector,uint32_t fresh_id_for_bvec3_selector,uint32_t fresh_id_for_bvec4_selector,const std::vector<protobufs::SideEffectWrapperInfo> & side_effect_wrappers_info)27 TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch(
28 uint32_t header_block_id, bool true_branch_first,
29 uint32_t fresh_id_for_bvec2_selector, uint32_t fresh_id_for_bvec3_selector,
30 uint32_t fresh_id_for_bvec4_selector,
31 const std::vector<protobufs::SideEffectWrapperInfo>&
32 side_effect_wrappers_info) {
33 message_.set_header_block_id(header_block_id);
34 message_.set_true_branch_first(true_branch_first);
35 message_.set_fresh_id_for_bvec2_selector(fresh_id_for_bvec2_selector);
36 message_.set_fresh_id_for_bvec3_selector(fresh_id_for_bvec3_selector);
37 message_.set_fresh_id_for_bvec4_selector(fresh_id_for_bvec4_selector);
38 for (auto const& side_effect_wrapper_info : side_effect_wrappers_info) {
39 *message_.add_side_effect_wrapper_info() = side_effect_wrapper_info;
40 }
41 }
42
IsApplicable(opt::IRContext * ir_context,const TransformationContext & transformation_context) const43 bool TransformationFlattenConditionalBranch::IsApplicable(
44 opt::IRContext* ir_context,
45 const TransformationContext& transformation_context) const {
46 auto header_block =
47 fuzzerutil::MaybeFindBlock(ir_context, message_.header_block_id());
48
49 // The block must have been found and it must be a selection header.
50 if (!header_block || !header_block->GetMergeInst() ||
51 header_block->GetMergeInst()->opcode() != SpvOpSelectionMerge) {
52 return false;
53 }
54
55 // The header block must end with an OpBranchConditional instruction.
56 if (header_block->terminator()->opcode() != SpvOpBranchConditional) {
57 return false;
58 }
59
60 // The branch condition cannot be irrelevant: we will make reference to it
61 // multiple times and we need to be guaranteed that these references will
62 // yield the same result; if they are replaced by other ids that will not
63 // work.
64 if (transformation_context.GetFactManager()->IdIsIrrelevant(
65 header_block->terminator()->GetSingleWordInOperand(0))) {
66 return false;
67 }
68
69 std::set<uint32_t> used_fresh_ids;
70
71 // If ids have been provided to be used as vector guards for OpSelect
72 // instructions then they must be fresh.
73 for (uint32_t fresh_id_for_bvec_selector :
74 {message_.fresh_id_for_bvec2_selector(),
75 message_.fresh_id_for_bvec3_selector(),
76 message_.fresh_id_for_bvec4_selector()}) {
77 if (fresh_id_for_bvec_selector != 0) {
78 if (!CheckIdIsFreshAndNotUsedByThisTransformation(
79 fresh_id_for_bvec_selector, ir_context, &used_fresh_ids)) {
80 return false;
81 }
82 }
83 }
84
85 // Use a set to keep track of the instructions that require fresh ids.
86 std::set<opt::Instruction*> instructions_that_need_ids;
87
88 // Check that, if there are enough ids, the conditional can be flattened and,
89 // if so, add all the problematic instructions that need to be enclosed inside
90 // conditionals to |instructions_that_need_ids|.
91 if (!GetProblematicInstructionsIfConditionalCanBeFlattened(
92 ir_context, header_block, transformation_context,
93 &instructions_that_need_ids)) {
94 return false;
95 }
96
97 // Get the mapping from instructions to the fresh ids needed to enclose them
98 // inside conditionals.
99 auto insts_to_wrapper_info = GetInstructionsToWrapperInfo(ir_context);
100
101 {
102 // Check the ids in the map.
103 for (const auto& inst_to_info : insts_to_wrapper_info) {
104 // Check the fresh ids needed for all of the instructions that need to be
105 // enclosed inside a conditional.
106 for (uint32_t id : {inst_to_info.second.merge_block_id(),
107 inst_to_info.second.execute_block_id()}) {
108 if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(
109 id, ir_context, &used_fresh_ids)) {
110 return false;
111 }
112 }
113
114 // Check the other ids needed, if the instruction needs a placeholder.
115 if (InstructionNeedsPlaceholder(ir_context, *inst_to_info.first)) {
116 // Check the fresh ids.
117 for (uint32_t id : {inst_to_info.second.actual_result_id(),
118 inst_to_info.second.alternative_block_id(),
119 inst_to_info.second.placeholder_result_id()}) {
120 if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(
121 id, ir_context, &used_fresh_ids)) {
122 return false;
123 }
124 }
125
126 // Check that the placeholder value id exists, has the right type and is
127 // available to use at this point.
128 auto value_def = ir_context->get_def_use_mgr()->GetDef(
129 inst_to_info.second.value_to_copy_id());
130 if (!value_def ||
131 value_def->type_id() != inst_to_info.first->type_id() ||
132 !fuzzerutil::IdIsAvailableBeforeInstruction(
133 ir_context, inst_to_info.first,
134 inst_to_info.second.value_to_copy_id())) {
135 return false;
136 }
137 }
138 }
139 }
140
141 // If some instructions that require ids are not in the map, the
142 // transformation needs overflow ids to be applicable.
143 for (auto instruction : instructions_that_need_ids) {
144 if (insts_to_wrapper_info.count(instruction) == 0 &&
145 !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
146 return false;
147 }
148 }
149
150 if (OpSelectArgumentsAreRestricted(ir_context)) {
151 // OpPhi instructions at the convergence block for the selection are handled
152 // by turning them into OpSelect instructions. As the SPIR-V version in use
153 // has restrictions on the arguments that OpSelect can take, we must check
154 // that any OpPhi instructions are compatible with these restrictions.
155 uint32_t convergence_block_id =
156 FindConvergenceBlock(ir_context, *header_block);
157 // Consider every OpPhi instruction at the convergence block.
158 if (!ir_context->cfg()
159 ->block(convergence_block_id)
160 ->WhileEachPhiInst([this,
161 ir_context](opt::Instruction* inst) -> bool {
162 // Decide whether the OpPhi can be handled based on its result
163 // type.
164 opt::Instruction* phi_result_type =
165 ir_context->get_def_use_mgr()->GetDef(inst->type_id());
166 switch (phi_result_type->opcode()) {
167 case SpvOpTypeBool:
168 case SpvOpTypeInt:
169 case SpvOpTypeFloat:
170 case SpvOpTypePointer:
171 // Fine: OpSelect can work directly on scalar and pointer
172 // types.
173 return true;
174 case SpvOpTypeVector: {
175 // In its restricted form, OpSelect can only select between
176 // vectors if the condition of the select is a boolean
177 // boolean vector. We thus require the appropriate boolean
178 // vector type to be present.
179 uint32_t bool_type_id =
180 fuzzerutil::MaybeGetBoolType(ir_context);
181 if (!bool_type_id) {
182 return false;
183 }
184
185 uint32_t dimension =
186 phi_result_type->GetSingleWordInOperand(1);
187 if (fuzzerutil::MaybeGetVectorType(ir_context, bool_type_id,
188 dimension) == 0) {
189 // The required boolean vector type is not present.
190 return false;
191 }
192 // The transformation needs to be equipped with a fresh id
193 // in which to store the vectorized version of the selection
194 // construct's condition.
195 switch (dimension) {
196 case 2:
197 return message_.fresh_id_for_bvec2_selector() != 0;
198 case 3:
199 return message_.fresh_id_for_bvec3_selector() != 0;
200 default:
201 assert(dimension == 4 && "Invalid vector dimension.");
202 return message_.fresh_id_for_bvec4_selector() != 0;
203 }
204 }
205 default:
206 return false;
207 }
208 })) {
209 return false;
210 }
211 }
212
213 // All checks were passed.
214 return true;
215 }
216
Apply(opt::IRContext * ir_context,TransformationContext * transformation_context) const217 void TransformationFlattenConditionalBranch::Apply(
218 opt::IRContext* ir_context,
219 TransformationContext* transformation_context) const {
220 // branch = 1 corresponds to the true branch, branch = 2 corresponds to the
221 // false branch. If the true branch is to be laid out first, we need to visit
222 // the false branch first, because each branch is moved to right after the
223 // header while it is visited.
224 std::vector<uint32_t> branches = {2, 1};
225 if (!message_.true_branch_first()) {
226 // Similarly, we need to visit the true branch first, if we want it to be
227 // laid out after the false branch.
228 branches = {1, 2};
229 }
230
231 auto header_block = ir_context->cfg()->block(message_.header_block_id());
232
233 // Get the ids of the starting blocks of the first and last branches to be
234 // laid out. The first branch is the true branch iff
235 // |message_.true_branch_first| is true.
236 auto branch_instruction = header_block->terminator();
237 uint32_t first_block_first_branch_id =
238 branch_instruction->GetSingleWordInOperand(branches[1]);
239 uint32_t first_block_last_branch_id =
240 branch_instruction->GetSingleWordInOperand(branches[0]);
241
242 uint32_t convergence_block_id =
243 FindConvergenceBlock(ir_context, *header_block);
244
245 // If the OpBranchConditional instruction in the header branches to the same
246 // block for both values of the condition, this is the convergence block (the
247 // flow does not actually diverge) and the OpPhi instructions in it are still
248 // valid, so we do not need to make any changes.
249 if (first_block_first_branch_id != first_block_last_branch_id) {
250 RewriteOpPhiInstructionsAtConvergenceBlock(
251 *header_block, convergence_block_id, ir_context);
252 }
253
254 // Get the mapping from instructions to fresh ids.
255 auto insts_to_info = GetInstructionsToWrapperInfo(ir_context);
256
257 // Get a reference to the last block in the first branch that will be laid out
258 // (this depends on |message_.true_branch_first|). The last block is the block
259 // in the branch just before flow converges (it might not exist).
260 opt::BasicBlock* last_block_first_branch = nullptr;
261
262 // Keep track of blocks and ids for which we should later add dead block and
263 // irrelevant id facts. We wait until we have finished applying the
264 // transformation before adding these facts, so that the fact manager has
265 // access to the fully up-to-date module.
266 std::vector<uint32_t> dead_blocks;
267 std::vector<uint32_t> irrelevant_ids;
268
269 // Adjust the conditional branches by enclosing problematic instructions
270 // within conditionals and get references to the last block in each branch.
271 for (uint32_t branch : branches) {
272 auto current_block = header_block;
273 // Get the id of the first block in this branch.
274 uint32_t next_block_id = branch_instruction->GetSingleWordInOperand(branch);
275
276 // Consider all blocks in the branch until the convergence block is reached.
277 while (next_block_id != convergence_block_id) {
278 // Move the next block to right after the current one.
279 current_block->GetParent()->MoveBasicBlockToAfter(next_block_id,
280 current_block);
281
282 // Move forward in the branch.
283 current_block = ir_context->cfg()->block(next_block_id);
284
285 // Find all the instructions in the current block which need to be
286 // enclosed inside conditionals.
287 std::vector<opt::Instruction*> problematic_instructions;
288
289 current_block->ForEachInst(
290 [&problematic_instructions](opt::Instruction* instruction) {
291 if (instruction->opcode() != SpvOpLabel &&
292 instruction->opcode() != SpvOpBranch &&
293 !fuzzerutil::InstructionHasNoSideEffects(*instruction)) {
294 problematic_instructions.push_back(instruction);
295 }
296 });
297
298 uint32_t condition_id =
299 header_block->terminator()->GetSingleWordInOperand(0);
300
301 // Enclose all of the problematic instructions in conditionals, with the
302 // same condition as the selection construct being flattened.
303 for (auto instruction : problematic_instructions) {
304 // Get the info needed by this instruction to wrap it inside a
305 // conditional.
306 protobufs::SideEffectWrapperInfo wrapper_info;
307
308 if (insts_to_info.count(instruction) != 0) {
309 // Get the fresh ids from the map, if present.
310 wrapper_info = insts_to_info[instruction];
311 } else {
312 // If we could not get it from the map, use overflow ids. We don't
313 // need to set |wrapper_info.instruction|, as it will not be used.
314 wrapper_info.set_merge_block_id(
315 transformation_context->GetOverflowIdSource()
316 ->GetNextOverflowId());
317 wrapper_info.set_execute_block_id(
318 transformation_context->GetOverflowIdSource()
319 ->GetNextOverflowId());
320
321 if (InstructionNeedsPlaceholder(ir_context, *instruction)) {
322 // Ge the fresh ids from the overflow ids.
323 wrapper_info.set_actual_result_id(
324 transformation_context->GetOverflowIdSource()
325 ->GetNextOverflowId());
326 wrapper_info.set_alternative_block_id(
327 transformation_context->GetOverflowIdSource()
328 ->GetNextOverflowId());
329 wrapper_info.set_placeholder_result_id(
330 transformation_context->GetOverflowIdSource()
331 ->GetNextOverflowId());
332
333 // Try to find a zero constant. It does not matter whether it is
334 // relevant or irrelevant.
335 for (bool is_irrelevant : {true, false}) {
336 wrapper_info.set_value_to_copy_id(
337 fuzzerutil::MaybeGetZeroConstant(
338 ir_context, *transformation_context,
339 instruction->type_id(), is_irrelevant));
340 if (wrapper_info.value_to_copy_id()) {
341 break;
342 }
343 }
344 }
345 }
346
347 // Enclose the instruction in a conditional and get the merge block
348 // generated by this operation (this is where all the following
349 // instructions will be).
350 current_block = EncloseInstructionInConditional(
351 ir_context, *transformation_context, current_block, instruction,
352 wrapper_info, condition_id, branch == 1, &dead_blocks,
353 &irrelevant_ids);
354 }
355
356 next_block_id = current_block->terminator()->GetSingleWordInOperand(0);
357
358 // If the next block is the convergence block and this the branch that
359 // will be laid out right after the header, record this as the last block
360 // in the first branch.
361 if (next_block_id == convergence_block_id && branch == branches[1]) {
362 last_block_first_branch = current_block;
363 }
364 }
365 }
366
367 // The current header should unconditionally branch to the starting block in
368 // the first branch to be laid out, if such a branch exists (i.e. the header
369 // does not branch directly to the convergence block), and to the starting
370 // block in the last branch to be laid out otherwise.
371 uint32_t after_header = first_block_first_branch_id != convergence_block_id
372 ? first_block_first_branch_id
373 : first_block_last_branch_id;
374
375 // Kill the merge instruction and the branch instruction in the current
376 // header.
377 auto merge_inst = header_block->GetMergeInst();
378 ir_context->KillInst(branch_instruction);
379 ir_context->KillInst(merge_inst);
380
381 // Add a new, unconditional, branch instruction from the current header to
382 // |after_header|.
383 header_block->AddInstruction(MakeUnique<opt::Instruction>(
384 ir_context, SpvOpBranch, 0, 0,
385 opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {after_header}}}));
386
387 // If the first branch to be laid out exists, change the branch instruction so
388 // that the last block in such branch unconditionally branches to the first
389 // block in the other branch (or the convergence block if there is no other
390 // branch) and change the OpPhi instructions in the last branch accordingly
391 // (the predecessor changed).
392 if (last_block_first_branch) {
393 last_block_first_branch->terminator()->SetInOperand(
394 0, {first_block_last_branch_id});
395
396 // Change the OpPhi instructions of the last branch (if there is another
397 // branch) so that the predecessor is now the last block of the first
398 // branch. The block must have a single predecessor, so the operand
399 // specifying the predecessor is always in the same position.
400 if (first_block_last_branch_id != convergence_block_id) {
401 ir_context->get_instr_block(first_block_last_branch_id)
402 ->ForEachPhiInst(
403 [&last_block_first_branch](opt::Instruction* phi_inst) {
404 // The operand specifying the predecessor is the second input
405 // operand.
406 phi_inst->SetInOperand(1, {last_block_first_branch->id()});
407 });
408 }
409 }
410
411 // Invalidate all analyses
412 ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
413
414 // Now that we have finished adding blocks and ids to the module and
415 // invalidated existing analyses, update the fact manager regarding dead
416 // blocks and irrelevant ids.
417 for (auto dead_block : dead_blocks) {
418 transformation_context->GetFactManager()->AddFactBlockIsDead(dead_block);
419 }
420 for (auto irrelevant_id : irrelevant_ids) {
421 transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
422 irrelevant_id);
423 }
424 }
425
ToMessage() const426 protobufs::Transformation TransformationFlattenConditionalBranch::ToMessage()
427 const {
428 protobufs::Transformation result;
429 *result.mutable_flatten_conditional_branch() = message_;
430 return result;
431 }
432
433 bool TransformationFlattenConditionalBranch::
GetProblematicInstructionsIfConditionalCanBeFlattened(opt::IRContext * ir_context,opt::BasicBlock * header,const TransformationContext & transformation_context,std::set<opt::Instruction * > * instructions_that_need_ids)434 GetProblematicInstructionsIfConditionalCanBeFlattened(
435 opt::IRContext* ir_context, opt::BasicBlock* header,
436 const TransformationContext& transformation_context,
437 std::set<opt::Instruction*>* instructions_that_need_ids) {
438 uint32_t merge_block_id = header->MergeBlockIdIfAny();
439 assert(merge_block_id &&
440 header->GetMergeInst()->opcode() == SpvOpSelectionMerge &&
441 header->terminator()->opcode() == SpvOpBranchConditional &&
442 "|header| must be the header of a conditional.");
443
444 auto enclosing_function = header->GetParent();
445 auto dominator_analysis =
446 ir_context->GetDominatorAnalysis(enclosing_function);
447 auto postdominator_analysis =
448 ir_context->GetPostDominatorAnalysis(enclosing_function);
449
450 // |header| must be reachable.
451 if (!dominator_analysis->IsReachable(header)) {
452 return false;
453 }
454
455 // Check that the header and the merge block describe a single-entry,
456 // single-exit region.
457 if (!dominator_analysis->Dominates(header->id(), merge_block_id) ||
458 !postdominator_analysis->Dominates(merge_block_id, header->id())) {
459 return false;
460 }
461
462 // Traverse the CFG starting from the header and check that, for all the
463 // blocks that can be reached by the header before the flow converges:
464 // - they don't contain merge, barrier or OpSampledImage instructions
465 // - they branch unconditionally to another block
466 // Add any side-effecting instruction, requiring fresh ids, to
467 // |instructions_that_need_ids|
468 std::queue<uint32_t> to_check;
469 header->ForEachSuccessorLabel(
470 [&to_check](uint32_t label) { to_check.push(label); });
471
472 auto* structured_cfg = ir_context->GetStructuredCFGAnalysis();
473 while (!to_check.empty()) {
474 uint32_t block_id = to_check.front();
475 to_check.pop();
476
477 if (structured_cfg->ContainingConstruct(block_id) != header->id() &&
478 block_id != merge_block_id) {
479 // This block can be reached from the |header| but doesn't belong to its
480 // selection construct. This might be a continue target of some loop -
481 // we can't flatten the |header|.
482 return false;
483 }
484
485 // If the block post-dominates the header, this is where flow converges, and
486 // we don't need to check this branch any further, because the
487 // transformation will only change the part of the graph where flow is
488 // divergent.
489 if (postdominator_analysis->Dominates(block_id, header->id())) {
490 continue;
491 }
492
493 if (!transformation_context.GetFactManager()->BlockIsDead(header->id()) &&
494 transformation_context.GetFactManager()->BlockIsDead(block_id)) {
495 // The |header| is not dead but the |block_id| is. Since |block_id|
496 // doesn't postdominate the |header|, CFG hasn't converged yet. Thus, we
497 // don't flatten the construct to prevent |block_id| from becoming
498 // executable.
499 return false;
500 }
501
502 auto block = ir_context->cfg()->block(block_id);
503
504 // The block must not have a merge instruction, because inner constructs are
505 // not allowed.
506 if (block->GetMergeInst()) {
507 return false;
508 }
509
510 // The terminator instruction for the block must be OpBranch.
511 if (block->terminator()->opcode() != SpvOpBranch) {
512 return false;
513 }
514
515 // The base objects for all data descriptors involved in synonym facts.
516 std::unordered_set<uint32_t> synonym_base_objects;
517 for (auto* synonym :
518 transformation_context.GetFactManager()->GetAllSynonyms()) {
519 synonym_base_objects.insert(synonym->object());
520 }
521
522 // Check all of the instructions in the block.
523 bool all_instructions_compatible = block->WhileEachInst(
524 [ir_context, instructions_that_need_ids,
525 &synonym_base_objects](opt::Instruction* instruction) {
526 // We can ignore OpLabel instructions.
527 if (instruction->opcode() == SpvOpLabel) {
528 return true;
529 }
530
531 // If the instruction is the base object of some synonym then we
532 // conservatively bail out: if a synonym ends up depending on an
533 // instruction that needs to be enclosed in a side-effect wrapper then
534 // it might no longer hold after we flatten the conditional.
535 if (instruction->result_id() &&
536 synonym_base_objects.count(instruction->result_id())) {
537 return false;
538 }
539
540 // If the instruction is a branch, it must be an unconditional branch.
541 if (instruction->IsBranch()) {
542 return instruction->opcode() == SpvOpBranch;
543 }
544
545 // We cannot go ahead if we encounter an instruction that cannot be
546 // handled.
547 if (!InstructionCanBeHandled(ir_context, *instruction)) {
548 return false;
549 }
550
551 // If the instruction has side effects, add it to the
552 // |instructions_that_need_ids| set.
553 if (!fuzzerutil::InstructionHasNoSideEffects(*instruction)) {
554 instructions_that_need_ids->emplace(instruction);
555 }
556
557 return true;
558 });
559
560 if (!all_instructions_compatible) {
561 return false;
562 }
563
564 // Add the successor of this block to the list of blocks that need to be
565 // checked.
566 to_check.push(block->terminator()->GetSingleWordInOperand(0));
567 }
568
569 // All the blocks are compatible with the transformation and this is indeed a
570 // single-entry, single-exit region.
571 return true;
572 }
573
InstructionNeedsPlaceholder(opt::IRContext * ir_context,const opt::Instruction & instruction)574 bool TransformationFlattenConditionalBranch::InstructionNeedsPlaceholder(
575 opt::IRContext* ir_context, const opt::Instruction& instruction) {
576 assert(!fuzzerutil::InstructionHasNoSideEffects(instruction) &&
577 InstructionCanBeHandled(ir_context, instruction) &&
578 "The instruction must have side effects and it must be possible to "
579 "enclose it inside a conditional.");
580
581 if (instruction.HasResultId()) {
582 // We need a placeholder iff the type is not Void.
583 auto type = ir_context->get_type_mgr()->GetType(instruction.type_id());
584 return type && !type->AsVoid();
585 }
586
587 return false;
588 }
589
590 std::unordered_map<opt::Instruction*, protobufs::SideEffectWrapperInfo>
GetInstructionsToWrapperInfo(opt::IRContext * ir_context) const591 TransformationFlattenConditionalBranch::GetInstructionsToWrapperInfo(
592 opt::IRContext* ir_context) const {
593 std::unordered_map<opt::Instruction*, protobufs::SideEffectWrapperInfo>
594 instructions_to_ids;
595 for (const auto& wrapper_info : message_.side_effect_wrapper_info()) {
596 auto instruction = FindInstruction(wrapper_info.instruction(), ir_context);
597 if (instruction) {
598 instructions_to_ids.emplace(instruction, wrapper_info);
599 }
600 }
601
602 return instructions_to_ids;
603 }
604
605 opt::BasicBlock*
EncloseInstructionInConditional(opt::IRContext * ir_context,const TransformationContext & transformation_context,opt::BasicBlock * block,opt::Instruction * instruction,const protobufs::SideEffectWrapperInfo & wrapper_info,uint32_t condition_id,bool exec_if_cond_true,std::vector<uint32_t> * dead_blocks,std::vector<uint32_t> * irrelevant_ids)606 TransformationFlattenConditionalBranch::EncloseInstructionInConditional(
607 opt::IRContext* ir_context,
608 const TransformationContext& transformation_context, opt::BasicBlock* block,
609 opt::Instruction* instruction,
610 const protobufs::SideEffectWrapperInfo& wrapper_info, uint32_t condition_id,
611 bool exec_if_cond_true, std::vector<uint32_t>* dead_blocks,
612 std::vector<uint32_t>* irrelevant_ids) {
613 // Get the next instruction (it will be useful for splitting).
614 auto next_instruction = instruction->NextNode();
615
616 // Update the module id bound.
617 for (uint32_t id :
618 {wrapper_info.merge_block_id(), wrapper_info.execute_block_id()}) {
619 fuzzerutil::UpdateModuleIdBound(ir_context, id);
620 }
621
622 // Create the block where the instruction is executed by splitting the
623 // original block.
624 auto execute_block = block->SplitBasicBlock(
625 ir_context, wrapper_info.execute_block_id(),
626 fuzzerutil::GetIteratorForInstruction(block, instruction));
627
628 // Create the merge block for the conditional that we are about to create by
629 // splitting execute_block (this will leave |instruction| as the only
630 // instruction in |execute_block|).
631 auto merge_block = execute_block->SplitBasicBlock(
632 ir_context, wrapper_info.merge_block_id(),
633 fuzzerutil::GetIteratorForInstruction(execute_block, next_instruction));
634
635 // Propagate the fact that the block is dead to the newly-created blocks.
636 if (transformation_context.GetFactManager()->BlockIsDead(block->id())) {
637 dead_blocks->emplace_back(execute_block->id());
638 dead_blocks->emplace_back(merge_block->id());
639 }
640
641 // Initially, consider the merge block as the alternative block to branch to
642 // if the instruction should not be executed.
643 auto alternative_block = merge_block;
644
645 // Add an unconditional branch from |execute_block| to |merge_block|.
646 execute_block->AddInstruction(MakeUnique<opt::Instruction>(
647 ir_context, SpvOpBranch, 0, 0,
648 opt::Instruction::OperandList{
649 {SPV_OPERAND_TYPE_ID, {merge_block->id()}}}));
650
651 // If the instruction requires a placeholder, it means that it has a result id
652 // and its result needs to be able to be used later on, and we need to:
653 // - add an additional block |ids.alternative_block_id| where a placeholder
654 // result id (using fresh id |ids.placeholder_result_id|) is obtained either
655 // by using OpCopyObject and copying |ids.value_to_copy_id| or, if such id
656 // was not given and a suitable constant was not found, by using OpUndef.
657 // - mark |ids.placeholder_result_id| as irrelevant
658 // - change the result id of the instruction to a fresh id
659 // (|ids.actual_result_id|).
660 // - add an OpPhi instruction, which will have the original result id of the
661 // instruction, in the merge block.
662 if (InstructionNeedsPlaceholder(ir_context, *instruction)) {
663 // Update the module id bound with the additional ids.
664 for (uint32_t id :
665 {wrapper_info.actual_result_id(), wrapper_info.alternative_block_id(),
666 wrapper_info.placeholder_result_id()}) {
667 fuzzerutil::UpdateModuleIdBound(ir_context, id);
668 }
669
670 // Create a new block using |fresh_ids.alternative_block_id| for its label.
671 auto alternative_block_temp =
672 MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
673 ir_context, SpvOpLabel, 0, wrapper_info.alternative_block_id(),
674 opt::Instruction::OperandList{}));
675
676 // Keep the original result id of the instruction in a variable.
677 uint32_t original_result_id = instruction->result_id();
678
679 // Set the result id of the instruction to be |ids.actual_result_id|.
680 instruction->SetResultId(wrapper_info.actual_result_id());
681
682 // Add a placeholder instruction, with the same type as the original
683 // instruction and id |ids.placeholder_result_id|, to the new block.
684 if (wrapper_info.value_to_copy_id()) {
685 // If there is an available id to copy from, the placeholder instruction
686 // will be %placeholder_result_id = OpCopyObject %type %value_to_copy_id
687 alternative_block_temp->AddInstruction(MakeUnique<opt::Instruction>(
688 ir_context, SpvOpCopyObject, instruction->type_id(),
689 wrapper_info.placeholder_result_id(),
690 opt::Instruction::OperandList{
691 {SPV_OPERAND_TYPE_ID, {wrapper_info.value_to_copy_id()}}}));
692 } else {
693 // If there is no such id, use an OpUndef instruction.
694 alternative_block_temp->AddInstruction(MakeUnique<opt::Instruction>(
695 ir_context, SpvOpUndef, instruction->type_id(),
696 wrapper_info.placeholder_result_id(),
697 opt::Instruction::OperandList{}));
698 }
699
700 // Mark |ids.placeholder_result_id| as irrelevant.
701 irrelevant_ids->emplace_back(wrapper_info.placeholder_result_id());
702
703 // Add an unconditional branch from the new block to the merge block.
704 alternative_block_temp->AddInstruction(MakeUnique<opt::Instruction>(
705 ir_context, SpvOpBranch, 0, 0,
706 opt::Instruction::OperandList{
707 {SPV_OPERAND_TYPE_ID, {merge_block->id()}}}));
708
709 // Insert the block before the merge block.
710 alternative_block = block->GetParent()->InsertBasicBlockBefore(
711 std::move(alternative_block_temp), merge_block);
712
713 // Using the original instruction result id, add an OpPhi instruction to the
714 // merge block, which will either take the value of the result of the
715 // instruction or the placeholder value defined in the alternative block.
716 merge_block->begin().InsertBefore(MakeUnique<opt::Instruction>(
717 ir_context, SpvOpPhi, instruction->type_id(), original_result_id,
718 opt::Instruction::OperandList{
719 {SPV_OPERAND_TYPE_ID, {instruction->result_id()}},
720 {SPV_OPERAND_TYPE_ID, {execute_block->id()}},
721 {SPV_OPERAND_TYPE_ID, {wrapper_info.placeholder_result_id()}},
722 {SPV_OPERAND_TYPE_ID, {alternative_block->id()}}}));
723
724 // Propagate the fact that the block is dead to the new block.
725 if (transformation_context.GetFactManager()->BlockIsDead(block->id())) {
726 dead_blocks->emplace_back(alternative_block->id());
727 }
728 }
729
730 // Depending on whether the instruction should be executed in the if branch or
731 // in the else branch, get the corresponding ids.
732 auto if_block_id = (exec_if_cond_true ? execute_block : alternative_block)
733 ->GetLabel()
734 ->result_id();
735 auto else_block_id = (exec_if_cond_true ? alternative_block : execute_block)
736 ->GetLabel()
737 ->result_id();
738
739 // Add an OpSelectionMerge instruction to the block.
740 block->AddInstruction(MakeUnique<opt::Instruction>(
741 ir_context, SpvOpSelectionMerge, 0, 0,
742 opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {merge_block->id()}},
743 {SPV_OPERAND_TYPE_SELECTION_CONTROL,
744 {SpvSelectionControlMaskNone}}}));
745
746 // Add an OpBranchConditional, to the block, using |condition_id| as the
747 // condition and branching to |if_block_id| if the condition is true and to
748 // |else_block_id| if the condition is false.
749 block->AddInstruction(MakeUnique<opt::Instruction>(
750 ir_context, SpvOpBranchConditional, 0, 0,
751 opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {condition_id}},
752 {SPV_OPERAND_TYPE_ID, {if_block_id}},
753 {SPV_OPERAND_TYPE_ID, {else_block_id}}}));
754
755 return merge_block;
756 }
757
InstructionCanBeHandled(opt::IRContext * ir_context,const opt::Instruction & instruction)758 bool TransformationFlattenConditionalBranch::InstructionCanBeHandled(
759 opt::IRContext* ir_context, const opt::Instruction& instruction) {
760 // We can handle all instructions with no side effects.
761 if (fuzzerutil::InstructionHasNoSideEffects(instruction)) {
762 return true;
763 }
764
765 // We cannot handle barrier instructions, while we should be able to handle
766 // all other instructions by enclosing them inside a conditional.
767 if (instruction.opcode() == SpvOpControlBarrier ||
768 instruction.opcode() == SpvOpMemoryBarrier ||
769 instruction.opcode() == SpvOpNamedBarrierInitialize ||
770 instruction.opcode() == SpvOpMemoryNamedBarrier ||
771 instruction.opcode() == SpvOpTypeNamedBarrier) {
772 return false;
773 }
774
775 // We cannot handle OpSampledImage instructions, as they need to be in the
776 // same block as their use.
777 if (instruction.opcode() == SpvOpSampledImage) {
778 return false;
779 }
780
781 // We cannot handle a sampled image load, because we re-work loads using
782 // conditional branches and OpPhi instructions, and the result type of OpPhi
783 // cannot be OpTypeSampledImage.
784 if (instruction.opcode() == SpvOpLoad &&
785 ir_context->get_def_use_mgr()->GetDef(instruction.type_id())->opcode() ==
786 SpvOpTypeSampledImage) {
787 return false;
788 }
789
790 // We cannot handle instructions with an id which return a void type, if the
791 // result id is used in the module (e.g. a function call to a function that
792 // returns nothing).
793 if (instruction.HasResultId()) {
794 auto type = ir_context->get_type_mgr()->GetType(instruction.type_id());
795 assert(type && "The type should be found in the module");
796
797 if (type->AsVoid() &&
798 !ir_context->get_def_use_mgr()->WhileEachUse(
799 instruction.result_id(),
800 [](opt::Instruction* use_inst, uint32_t use_index) {
801 // Return false if the id is used as an input operand.
802 return use_index <
803 use_inst->NumOperands() - use_inst->NumInOperands();
804 })) {
805 return false;
806 }
807 }
808
809 return true;
810 }
811
812 std::unordered_set<uint32_t>
GetFreshIds() const813 TransformationFlattenConditionalBranch::GetFreshIds() const {
814 std::unordered_set<uint32_t> result = {
815 message_.fresh_id_for_bvec2_selector(),
816 message_.fresh_id_for_bvec3_selector(),
817 message_.fresh_id_for_bvec4_selector()};
818 for (auto& side_effect_wrapper_info : message_.side_effect_wrapper_info()) {
819 result.insert(side_effect_wrapper_info.merge_block_id());
820 result.insert(side_effect_wrapper_info.execute_block_id());
821 result.insert(side_effect_wrapper_info.actual_result_id());
822 result.insert(side_effect_wrapper_info.alternative_block_id());
823 result.insert(side_effect_wrapper_info.placeholder_result_id());
824 }
825 return result;
826 }
827
FindConvergenceBlock(opt::IRContext * ir_context,const opt::BasicBlock & header_block)828 uint32_t TransformationFlattenConditionalBranch::FindConvergenceBlock(
829 opt::IRContext* ir_context, const opt::BasicBlock& header_block) {
830 uint32_t result = header_block.terminator()->GetSingleWordInOperand(1);
831 auto postdominator_analysis =
832 ir_context->GetPostDominatorAnalysis(header_block.GetParent());
833 while (!postdominator_analysis->Dominates(result, header_block.id())) {
834 auto current_block = ir_context->get_instr_block(result);
835 // If the transformation is applicable, the terminator is OpBranch.
836 result = current_block->terminator()->GetSingleWordInOperand(0);
837 }
838 return result;
839 }
840
OpSelectArgumentsAreRestricted(opt::IRContext * ir_context)841 bool TransformationFlattenConditionalBranch::OpSelectArgumentsAreRestricted(
842 opt::IRContext* ir_context) {
843 switch (ir_context->grammar().target_env()) {
844 case SPV_ENV_UNIVERSAL_1_0:
845 case SPV_ENV_UNIVERSAL_1_1:
846 case SPV_ENV_UNIVERSAL_1_2:
847 case SPV_ENV_UNIVERSAL_1_3:
848 case SPV_ENV_VULKAN_1_0:
849 case SPV_ENV_VULKAN_1_1: {
850 return true;
851 }
852 default:
853 return false;
854 }
855 }
856
AddBooleanVectorConstructorToBlock(uint32_t fresh_id,uint32_t dimension,const opt::Operand & branch_condition_operand,opt::IRContext * ir_context,opt::BasicBlock * block)857 void TransformationFlattenConditionalBranch::AddBooleanVectorConstructorToBlock(
858 uint32_t fresh_id, uint32_t dimension,
859 const opt::Operand& branch_condition_operand, opt::IRContext* ir_context,
860 opt::BasicBlock* block) {
861 opt::Instruction::OperandList in_operands;
862 for (uint32_t i = 0; i < dimension; i++) {
863 in_operands.emplace_back(branch_condition_operand);
864 }
865 block->begin()->InsertBefore(MakeUnique<opt::Instruction>(
866 ir_context, SpvOpCompositeConstruct,
867 fuzzerutil::MaybeGetVectorType(
868 ir_context, fuzzerutil::MaybeGetBoolType(ir_context), dimension),
869 fresh_id, in_operands));
870 fuzzerutil::UpdateModuleIdBound(ir_context, fresh_id);
871 }
872
873 void TransformationFlattenConditionalBranch::
RewriteOpPhiInstructionsAtConvergenceBlock(const opt::BasicBlock & header_block,uint32_t convergence_block_id,opt::IRContext * ir_context) const874 RewriteOpPhiInstructionsAtConvergenceBlock(
875 const opt::BasicBlock& header_block, uint32_t convergence_block_id,
876 opt::IRContext* ir_context) const {
877 const opt::Instruction& branch_instruction = *header_block.terminator();
878
879 const opt::Operand& branch_condition_operand =
880 branch_instruction.GetInOperand(0);
881
882 // If we encounter OpPhi instructions on vector types then we may need to
883 // introduce vector versions of the selection construct's condition to use
884 // in corresponding OpSelect instructions. These booleans track whether we
885 // need to introduce such boolean vectors.
886 bool require_2d_boolean_vector = false;
887 bool require_3d_boolean_vector = false;
888 bool require_4d_boolean_vector = false;
889
890 // Consider every OpPhi instruction at the convergence block.
891 opt::BasicBlock* convergence_block =
892 ir_context->get_instr_block(convergence_block_id);
893 convergence_block->ForEachPhiInst(
894 [this, &branch_condition_operand, branch_instruction,
895 convergence_block_id, &header_block, ir_context,
896 &require_2d_boolean_vector, &require_3d_boolean_vector,
897 &require_4d_boolean_vector](opt::Instruction* phi_inst) {
898 assert(phi_inst->NumInOperands() == 4 &&
899 "We are going to replace an OpPhi with an OpSelect. This "
900 "only makes sense if the block has two distinct "
901 "predecessors.");
902 // We are going to replace the OpPhi with an OpSelect. By default,
903 // the condition for the OpSelect will be the branch condition's
904 // operand. However, if the OpPhi has vector result type we may need
905 // to use a boolean vector as the condition instead.
906 opt::Operand selector_operand = branch_condition_operand;
907 opt::Instruction* type_inst =
908 ir_context->get_def_use_mgr()->GetDef(phi_inst->type_id());
909 if (type_inst->opcode() == SpvOpTypeVector) {
910 uint32_t dimension = type_inst->GetSingleWordInOperand(1);
911 switch (dimension) {
912 case 2:
913 // The OpPhi's result type is a 2D vector. If a fresh id for a
914 // bvec2 selector was provided then we should use it as the
915 // OpSelect's condition, and note the fact that we will need to
916 // add an instruction to bring this bvec2 into existence.
917 if (message_.fresh_id_for_bvec2_selector() != 0) {
918 selector_operand = {SPV_OPERAND_TYPE_ID,
919 {message_.fresh_id_for_bvec2_selector()}};
920 require_2d_boolean_vector = true;
921 }
922 break;
923 case 3:
924 // Similar to the 2D case.
925 if (message_.fresh_id_for_bvec3_selector() != 0) {
926 selector_operand = {SPV_OPERAND_TYPE_ID,
927 {message_.fresh_id_for_bvec3_selector()}};
928 require_3d_boolean_vector = true;
929 }
930 break;
931 case 4:
932 // Similar to the 2D case.
933 if (message_.fresh_id_for_bvec4_selector() != 0) {
934 selector_operand = {SPV_OPERAND_TYPE_ID,
935 {message_.fresh_id_for_bvec4_selector()}};
936 require_4d_boolean_vector = true;
937 }
938 break;
939 default:
940 assert(dimension == 4 && "Invalid vector dimension.");
941 break;
942 }
943 }
944 std::vector<opt::Operand> operands;
945 operands.emplace_back(selector_operand);
946
947 uint32_t branch_instruction_true_block_id =
948 branch_instruction.GetSingleWordInOperand(1);
949 uint32_t branch_instruction_false_block_id =
950 branch_instruction.GetSingleWordInOperand(2);
951
952 // The OpPhi takes values from two distinct predecessors. One
953 // predecessor is associated with the "true" path of the conditional
954 // we are flattening, the other with the "false" path, but these
955 // predecessors can appear in either order as operands to the OpPhi
956 // instruction. We determine in which order the OpPhi inputs should
957 // appear as OpSelect arguments by first checking whether the
958 // convergence block is a direct successor of the selection header, and
959 // otherwise checking dominance of the true and false immediate
960 // successors of the header block.
961 if (branch_instruction_true_block_id == convergence_block_id) {
962 // The branch instruction's true block is the convergence block. This
963 // means that the OpPhi's value associated with the branch
964 // instruction's block should the "true" result of the OpSelect.
965 assert(branch_instruction_false_block_id != convergence_block_id &&
966 "Control should not reach here if both branches target the "
967 "convergence block.");
968 if (phi_inst->GetSingleWordInOperand(1) ==
969 message_.header_block_id()) {
970 operands.emplace_back(phi_inst->GetInOperand(0));
971 operands.emplace_back(phi_inst->GetInOperand(2));
972 } else {
973 assert(phi_inst->GetSingleWordInOperand(3) ==
974 message_.header_block_id() &&
975 "Since the convergence block has the header block as one of "
976 "two predecessors, if it is not handled by the first pair "
977 "of operands of this OpPhi instruction it should be handled "
978 "by the second pair.");
979 operands.emplace_back(phi_inst->GetInOperand(2));
980 operands.emplace_back(phi_inst->GetInOperand(0));
981 }
982 } else if (branch_instruction_false_block_id == convergence_block_id) {
983 // The branch instruction's false block is the convergence block. This
984 // means that the OpPhi's value associated with the branch
985 // instruction's block should the "false" result of the OpSelect.
986 if (phi_inst->GetSingleWordInOperand(1) ==
987 message_.header_block_id()) {
988 operands.emplace_back(phi_inst->GetInOperand(2));
989 operands.emplace_back(phi_inst->GetInOperand(0));
990 } else {
991 assert(phi_inst->GetSingleWordInOperand(3) ==
992 message_.header_block_id() &&
993 "Since the convergence block has the header block as one of "
994 "two predecessors, if it is not handled by the first pair "
995 "of operands of this OpPhi instruction it should be handled "
996 "by the second pair.");
997 operands.emplace_back(phi_inst->GetInOperand(0));
998 operands.emplace_back(phi_inst->GetInOperand(2));
999 }
1000 } else if (ir_context->GetDominatorAnalysis(header_block.GetParent())
1001 ->Dominates(branch_instruction_true_block_id,
1002 phi_inst->GetSingleWordInOperand(1))) {
1003 // The "true" branch of the conditional is handled first in the
1004 // OpPhi's operands; we thus provide operands to OpSelect in the same
1005 // order that they appear in the OpPhi.
1006 operands.emplace_back(phi_inst->GetInOperand(0));
1007 operands.emplace_back(phi_inst->GetInOperand(2));
1008 } else {
1009 // The "false" branch of the conditional is handled first in the
1010 // OpPhi's operands; we thus provide operands to OpSelect in reverse
1011 // of the order that they appear in the OpPhi.
1012 operands.emplace_back(phi_inst->GetInOperand(2));
1013 operands.emplace_back(phi_inst->GetInOperand(0));
1014 }
1015 phi_inst->SetOpcode(SpvOpSelect);
1016 phi_inst->SetInOperands(std::move(operands));
1017 });
1018
1019 // Add boolean vector instructions to the start of the block as required.
1020 if (require_2d_boolean_vector) {
1021 AddBooleanVectorConstructorToBlock(message_.fresh_id_for_bvec2_selector(),
1022 2, branch_condition_operand, ir_context,
1023 convergence_block);
1024 }
1025 if (require_3d_boolean_vector) {
1026 AddBooleanVectorConstructorToBlock(message_.fresh_id_for_bvec3_selector(),
1027 3, branch_condition_operand, ir_context,
1028 convergence_block);
1029 }
1030 if (require_4d_boolean_vector) {
1031 AddBooleanVectorConstructorToBlock(message_.fresh_id_for_bvec4_selector(),
1032 4, branch_condition_operand, ir_context,
1033 convergence_block);
1034 }
1035 }
1036
1037 } // namespace fuzz
1038 } // namespace spvtools
1039