• 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/transformation_replace_opselect_with_conditional_branch.h"
16 
17 #include "source/fuzz/fuzzer_util.h"
18 
19 namespace spvtools {
20 namespace fuzz {
21 TransformationReplaceOpSelectWithConditionalBranch::
TransformationReplaceOpSelectWithConditionalBranch(protobufs::TransformationReplaceOpSelectWithConditionalBranch message)22     TransformationReplaceOpSelectWithConditionalBranch(
23         protobufs::TransformationReplaceOpSelectWithConditionalBranch message)
24     : message_(std::move(message)) {}
25 
26 TransformationReplaceOpSelectWithConditionalBranch::
TransformationReplaceOpSelectWithConditionalBranch(uint32_t select_id,uint32_t true_block_id,uint32_t false_block_id)27     TransformationReplaceOpSelectWithConditionalBranch(
28         uint32_t select_id, uint32_t true_block_id, uint32_t false_block_id) {
29   message_.set_select_id(select_id);
30   message_.set_true_block_id(true_block_id);
31   message_.set_false_block_id(false_block_id);
32 }
33 
IsApplicable(opt::IRContext * ir_context,const TransformationContext &) const34 bool TransformationReplaceOpSelectWithConditionalBranch::IsApplicable(
35     opt::IRContext* ir_context,
36     const TransformationContext& /* unused */) const {
37   assert((message_.true_block_id() || message_.false_block_id()) &&
38          "At least one of the ids must be non-zero.");
39 
40   // Check that the non-zero ids are fresh.
41   std::set<uint32_t> used_ids;
42   for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) {
43     if (id && !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
44                                                             &used_ids)) {
45       return false;
46     }
47   }
48 
49   auto instruction =
50       ir_context->get_def_use_mgr()->GetDef(message_.select_id());
51 
52   // The instruction must exist and it must be an OpSelect instruction.
53   if (!instruction || instruction->opcode() != spv::Op::OpSelect) {
54     return false;
55   }
56 
57   // Check that the condition is a scalar boolean.
58   auto condition = ir_context->get_def_use_mgr()->GetDef(
59       instruction->GetSingleWordInOperand(0));
60   assert(condition && "The condition should always exist in a valid module.");
61 
62   auto condition_type =
63       ir_context->get_type_mgr()->GetType(condition->type_id());
64   if (!condition_type->AsBool()) {
65     return false;
66   }
67 
68   auto block = ir_context->get_instr_block(instruction);
69   assert(block && "The block containing the instruction must be found");
70 
71   // The instruction must be the first in its block.
72   if (instruction->unique_id() != block->begin()->unique_id()) {
73     return false;
74   }
75 
76   // The block must not be a merge block.
77   if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) {
78     return false;
79   }
80 
81   // The block must have exactly one predecessor.
82   auto predecessors = ir_context->cfg()->preds(block->id());
83   if (predecessors.size() != 1) {
84     return false;
85   }
86 
87   uint32_t pred_id = predecessors[0];
88   auto predecessor = ir_context->get_instr_block(pred_id);
89 
90   // The predecessor must not be the header of a construct and it must end with
91   // OpBranch.
92   if (predecessor->GetMergeInst() != nullptr ||
93       predecessor->terminator()->opcode() != spv::Op::OpBranch) {
94     return false;
95   }
96 
97   return true;
98 }
99 
Apply(opt::IRContext * ir_context,TransformationContext *) const100 void TransformationReplaceOpSelectWithConditionalBranch::Apply(
101     opt::IRContext* ir_context, TransformationContext* /* unused */) const {
102   auto instruction =
103       ir_context->get_def_use_mgr()->GetDef(message_.select_id());
104 
105   auto block = ir_context->get_instr_block(instruction);
106 
107   auto predecessor =
108       ir_context->get_instr_block(ir_context->cfg()->preds(block->id())[0]);
109 
110   // Create a new block for each non-zero id in {|message_.true_branch_id|,
111   // |message_.false_branch_id|}. Make each newly-created block branch
112   // unconditionally to the instruction block.
113   for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) {
114     if (id) {
115       fuzzerutil::UpdateModuleIdBound(ir_context, id);
116 
117       // Create the new block.
118       auto new_block = MakeUnique<opt::BasicBlock>(
119           MakeUnique<opt::Instruction>(ir_context, spv::Op::OpLabel, 0, id,
120                                        opt::Instruction::OperandList{}));
121 
122       // Add an unconditional branch from the new block to the instruction
123       // block.
124       new_block->AddInstruction(MakeUnique<opt::Instruction>(
125           ir_context, spv::Op::OpBranch, 0, 0,
126           opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}}}));
127 
128       // Insert the new block right after the predecessor of the instruction
129       // block.
130       block->GetParent()->InsertBasicBlockBefore(std::move(new_block), block);
131     }
132   }
133 
134   // Delete the OpBranch instruction from the predecessor.
135   ir_context->KillInst(predecessor->terminator());
136 
137   // Add an OpSelectionMerge instruction to the predecessor block, where the
138   // merge block is the instruction block.
139   predecessor->AddInstruction(MakeUnique<opt::Instruction>(
140       ir_context, spv::Op::OpSelectionMerge, 0, 0,
141       opt::Instruction::OperandList{
142           {SPV_OPERAND_TYPE_ID, {block->id()}},
143           {SPV_OPERAND_TYPE_SELECTION_CONTROL,
144            {uint32_t(spv::SelectionControlMask::MaskNone)}}}));
145 
146   // |if_block| will be the true block, if it has been created, the instruction
147   // block otherwise.
148   uint32_t if_block =
149       message_.true_block_id() ? message_.true_block_id() : block->id();
150 
151   // |else_block| will be the false block, if it has been created, the
152   // instruction block otherwise.
153   uint32_t else_block =
154       message_.false_block_id() ? message_.false_block_id() : block->id();
155 
156   assert(if_block != else_block &&
157          "|if_block| and |else_block| should always be different, if the "
158          "transformation is applicable.");
159 
160   // Add a conditional branching instruction to the predecessor, branching to
161   // |if_block| if the condition is true and to |if_false| otherwise.
162   predecessor->AddInstruction(MakeUnique<opt::Instruction>(
163       ir_context, spv::Op::OpBranchConditional, 0, 0,
164       opt::Instruction::OperandList{
165           {SPV_OPERAND_TYPE_ID, {instruction->GetSingleWordInOperand(0)}},
166           {SPV_OPERAND_TYPE_ID, {if_block}},
167           {SPV_OPERAND_TYPE_ID, {else_block}}}));
168 
169   // |if_pred| will be the true block, if it has been created, the existing
170   // predecessor otherwise.
171   uint32_t if_pred =
172       message_.true_block_id() ? message_.true_block_id() : predecessor->id();
173 
174   // |else_pred| will be the false block, if it has been created, the existing
175   // predecessor otherwise.
176   uint32_t else_pred =
177       message_.false_block_id() ? message_.false_block_id() : predecessor->id();
178 
179   // Replace the OpSelect instruction in the merge block with an OpPhi.
180   // This:          OpSelect %type %cond %if %else
181   // will become:   OpPhi %type %if %if_pred %else %else_pred
182   instruction->SetOpcode(spv::Op::OpPhi);
183   std::vector<opt::Operand> operands;
184 
185   operands.emplace_back(instruction->GetInOperand(1));
186   operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {if_pred}});
187 
188   operands.emplace_back(instruction->GetInOperand(2));
189   operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {else_pred}});
190 
191   instruction->SetInOperands(std::move(operands));
192 
193   // Invalidate all analyses, since the structure of the module was changed.
194   ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
195 }
196 
197 protobufs::Transformation
ToMessage() const198 TransformationReplaceOpSelectWithConditionalBranch::ToMessage() const {
199   protobufs::Transformation result;
200   *result.mutable_replace_opselect_with_conditional_branch() = message_;
201   return result;
202 }
203 
204 std::unordered_set<uint32_t>
GetFreshIds() const205 TransformationReplaceOpSelectWithConditionalBranch::GetFreshIds() const {
206   return {message_.true_block_id(), message_.false_block_id()};
207 }
208 
209 }  // namespace fuzz
210 }  // namespace spvtools
211